perspectapi-ts-sdk 1.1.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.
package/dist/index.mjs ADDED
@@ -0,0 +1,1727 @@
1
+ // src/utils/http-client.ts
2
+ var HttpClient = class {
3
+ baseUrl;
4
+ defaultHeaders;
5
+ timeout;
6
+ retries;
7
+ constructor(config) {
8
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
9
+ this.timeout = config.timeout || 3e4;
10
+ this.retries = config.retries || 3;
11
+ this.defaultHeaders = {
12
+ "Content-Type": "application/json",
13
+ "User-Agent": "perspectapi-ts-sdk/1.0.0",
14
+ ...config.headers
15
+ };
16
+ if (config.apiKey) {
17
+ this.defaultHeaders["X-API-Key"] = config.apiKey;
18
+ }
19
+ if (config.jwt) {
20
+ this.defaultHeaders["Authorization"] = `Bearer ${config.jwt}`;
21
+ }
22
+ }
23
+ /**
24
+ * Update authentication token
25
+ */
26
+ setAuth(jwt) {
27
+ this.defaultHeaders["Authorization"] = `Bearer ${jwt}`;
28
+ }
29
+ /**
30
+ * Update API key
31
+ */
32
+ setApiKey(apiKey) {
33
+ this.defaultHeaders["X-API-Key"] = apiKey;
34
+ }
35
+ /**
36
+ * Remove authentication
37
+ */
38
+ clearAuth() {
39
+ delete this.defaultHeaders["Authorization"];
40
+ delete this.defaultHeaders["X-API-Key"];
41
+ }
42
+ /**
43
+ * Make HTTP request with retry logic
44
+ */
45
+ async request(endpoint, options = {}) {
46
+ const url = this.buildUrl(endpoint, options.params);
47
+ const requestOptions = this.buildRequestOptions(options);
48
+ console.log(`[HTTP Client] Making ${options.method || "GET"} request to: ${url}`);
49
+ console.log(`[HTTP Client] Base URL: ${this.baseUrl}`);
50
+ console.log(`[HTTP Client] Endpoint: ${endpoint}`);
51
+ console.log(`[HTTP Client] Full URL: ${url}`);
52
+ if (options.params) {
53
+ console.log(`[HTTP Client] Query params:`, options.params);
54
+ }
55
+ let lastError;
56
+ for (let attempt = 0; attempt <= this.retries; attempt++) {
57
+ try {
58
+ const response = await this.fetchWithTimeout(url, requestOptions);
59
+ return await this.handleResponse(response);
60
+ } catch (error) {
61
+ lastError = error;
62
+ if (error && typeof error === "object" && "status" in error && typeof error.status === "number" && error.status < 500) {
63
+ throw error;
64
+ }
65
+ if (attempt === this.retries) {
66
+ break;
67
+ }
68
+ await this.delay(Math.pow(2, attempt) * 1e3);
69
+ }
70
+ }
71
+ throw lastError;
72
+ }
73
+ /**
74
+ * GET request
75
+ */
76
+ async get(endpoint, params) {
77
+ return this.request(endpoint, { method: "GET", params });
78
+ }
79
+ /**
80
+ * POST request
81
+ */
82
+ async post(endpoint, body) {
83
+ return this.request(endpoint, { method: "POST", body });
84
+ }
85
+ /**
86
+ * PUT request
87
+ */
88
+ async put(endpoint, body) {
89
+ return this.request(endpoint, { method: "PUT", body });
90
+ }
91
+ /**
92
+ * DELETE request
93
+ */
94
+ async delete(endpoint) {
95
+ return this.request(endpoint, { method: "DELETE" });
96
+ }
97
+ /**
98
+ * PATCH request
99
+ */
100
+ async patch(endpoint, body) {
101
+ return this.request(endpoint, { method: "PATCH", body });
102
+ }
103
+ /**
104
+ * Build full URL with query parameters
105
+ */
106
+ buildUrl(endpoint, params) {
107
+ const url = `${this.baseUrl}${endpoint.startsWith("/") ? "" : "/"}${endpoint}`;
108
+ if (!params || Object.keys(params).length === 0) {
109
+ return url;
110
+ }
111
+ const searchParams = new URLSearchParams();
112
+ Object.entries(params).forEach(([key, value]) => {
113
+ if (value !== void 0 && value !== null) {
114
+ searchParams.append(key, String(value));
115
+ }
116
+ });
117
+ return `${url}?${searchParams.toString()}`;
118
+ }
119
+ /**
120
+ * Build request options
121
+ */
122
+ buildRequestOptions(options) {
123
+ const headers = {
124
+ ...this.defaultHeaders,
125
+ ...options.headers
126
+ };
127
+ const requestOptions = {
128
+ method: options.method || "GET",
129
+ headers
130
+ };
131
+ if (options.body && options.method !== "GET") {
132
+ if (typeof options.body === "string") {
133
+ requestOptions.body = options.body;
134
+ } else {
135
+ requestOptions.body = JSON.stringify(options.body);
136
+ }
137
+ }
138
+ return requestOptions;
139
+ }
140
+ /**
141
+ * Fetch with timeout support
142
+ */
143
+ async fetchWithTimeout(url, options) {
144
+ const controller = new AbortController();
145
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
146
+ try {
147
+ const response = await fetch(url, {
148
+ ...options,
149
+ signal: controller.signal
150
+ });
151
+ return response;
152
+ } finally {
153
+ clearTimeout(timeoutId);
154
+ }
155
+ }
156
+ /**
157
+ * Handle response and errors
158
+ */
159
+ async handleResponse(response) {
160
+ const contentType = response.headers.get("content-type");
161
+ const isJson = contentType?.includes("application/json");
162
+ let data;
163
+ try {
164
+ data = isJson ? await response.json() : await response.text();
165
+ } catch (error) {
166
+ data = null;
167
+ }
168
+ if (!response.ok) {
169
+ const error = {
170
+ message: data?.error || data?.message || `HTTP ${response.status}: ${response.statusText}`,
171
+ status: response.status,
172
+ code: data?.code,
173
+ details: data
174
+ };
175
+ throw error;
176
+ }
177
+ if (isJson && typeof data === "object") {
178
+ if ("data" in data || "message" in data || "error" in data) {
179
+ return data;
180
+ }
181
+ return { data, success: true };
182
+ }
183
+ return { data, success: true };
184
+ }
185
+ /**
186
+ * Delay utility for retries
187
+ */
188
+ delay(ms) {
189
+ return new Promise((resolve) => setTimeout(resolve, ms));
190
+ }
191
+ };
192
+ function createApiError(error) {
193
+ if (error instanceof Error) {
194
+ return error;
195
+ }
196
+ if (error && typeof error === "object" && "message" in error) {
197
+ return error;
198
+ }
199
+ return {
200
+ message: "Unknown error occurred",
201
+ details: error
202
+ };
203
+ }
204
+
205
+ // src/client/base-client.ts
206
+ var BaseClient = class {
207
+ http;
208
+ basePath;
209
+ constructor(http, basePath) {
210
+ this.http = http;
211
+ this.basePath = basePath;
212
+ }
213
+ /**
214
+ * Build a site-scoped endpoint relative to the API base path
215
+ */
216
+ siteScopedEndpoint(siteName, endpoint = "", options) {
217
+ if (!siteName || !siteName.trim()) {
218
+ throw new Error("siteName is required");
219
+ }
220
+ const encodedSiteName = encodeURIComponent(siteName.trim());
221
+ const cleanEndpoint = endpoint.replace(/^\//, "");
222
+ const includeSitesSegment = options?.includeSitesSegment ?? true;
223
+ const baseSegment = includeSitesSegment ? `sites/${encodedSiteName}` : encodedSiteName;
224
+ const suffix = cleanEndpoint ? `/${cleanEndpoint}` : "";
225
+ return `/${baseSegment}${suffix}`;
226
+ }
227
+ /**
228
+ * Build endpoint path
229
+ */
230
+ buildPath(endpoint) {
231
+ const cleanBasePath = this.basePath.replace(/\/$/, "");
232
+ const cleanEndpoint = endpoint.replace(/^\//, "");
233
+ return `${cleanBasePath}/${cleanEndpoint}`;
234
+ }
235
+ /**
236
+ * Handle paginated responses
237
+ */
238
+ async getPaginated(endpoint, params) {
239
+ return this.http.get(this.buildPath(endpoint), params);
240
+ }
241
+ /**
242
+ * Handle single resource responses
243
+ */
244
+ async getSingle(endpoint) {
245
+ return this.http.get(this.buildPath(endpoint));
246
+ }
247
+ /**
248
+ * Handle create operations
249
+ */
250
+ async create(endpoint, data) {
251
+ return this.http.post(this.buildPath(endpoint), data);
252
+ }
253
+ /**
254
+ * Handle update operations
255
+ */
256
+ async update(endpoint, data) {
257
+ return this.http.put(this.buildPath(endpoint), data);
258
+ }
259
+ /**
260
+ * Handle partial update operations
261
+ */
262
+ async patch(endpoint, data) {
263
+ return this.http.patch(this.buildPath(endpoint), data);
264
+ }
265
+ /**
266
+ * Handle delete operations
267
+ */
268
+ async delete(endpoint) {
269
+ return this.http.delete(this.buildPath(endpoint));
270
+ }
271
+ };
272
+
273
+ // src/client/auth-client.ts
274
+ var AuthClient = class extends BaseClient {
275
+ constructor(http) {
276
+ super(http, "/api/v1");
277
+ }
278
+ /**
279
+ * Get CSRF token
280
+ */
281
+ async getCsrfToken() {
282
+ return this.http.get(this.buildPath("/csrf"));
283
+ }
284
+ /**
285
+ * Sign up a new user
286
+ */
287
+ async signUp(data) {
288
+ return this.create("/signup", data);
289
+ }
290
+ /**
291
+ * Sign in user
292
+ */
293
+ async signIn(data) {
294
+ const response = await this.create("/authenticate", data);
295
+ if (response.data && "token" in response.data && response.data.token) {
296
+ this.http.setAuth(response.data.token);
297
+ }
298
+ return response;
299
+ }
300
+ /**
301
+ * Activate user account
302
+ */
303
+ async activateAccount(activationKey) {
304
+ return this.getSingle(`/activate/${activationKey}`);
305
+ }
306
+ /**
307
+ * Request password reset
308
+ */
309
+ async forgotPassword(email) {
310
+ return this.create("/forgotpassword", { email });
311
+ }
312
+ /**
313
+ * Reset password with token
314
+ */
315
+ async resetPassword(resetToken, password) {
316
+ return this.create(
317
+ `/passwordreset/${resetToken}`,
318
+ { password }
319
+ );
320
+ }
321
+ /**
322
+ * Get current user profile
323
+ */
324
+ async getUserProfile() {
325
+ return this.getSingle("/userprofile");
326
+ }
327
+ /**
328
+ * Sign out (clear local auth)
329
+ */
330
+ signOut() {
331
+ this.http.clearAuth();
332
+ }
333
+ };
334
+
335
+ // src/client/content-client.ts
336
+ var ContentClient = class extends BaseClient {
337
+ constructor(http) {
338
+ super(http, "/api/v1");
339
+ }
340
+ /**
341
+ * Get all content with pagination and filtering for a site
342
+ */
343
+ async getContent(siteName, params) {
344
+ return this.http.get(this.buildPath(this.siteScopedEndpoint(siteName)), params);
345
+ }
346
+ /**
347
+ * Get content by ID
348
+ */
349
+ async getContentById(id) {
350
+ return this.getSingle(`/content/${id}`);
351
+ }
352
+ /**
353
+ * Get content by slug for a site
354
+ */
355
+ async getContentBySlug(siteName, slug) {
356
+ return this.http.get(this.buildPath(
357
+ this.siteScopedEndpoint(siteName, `/slug/${encodeURIComponent(slug)}`)
358
+ ));
359
+ }
360
+ /**
361
+ * Create new content
362
+ */
363
+ async createContent(data) {
364
+ return this.create("/content", data);
365
+ }
366
+ /**
367
+ * Update content
368
+ */
369
+ async updateContent(id, data) {
370
+ return this.update(`/content/${id}`, data);
371
+ }
372
+ /**
373
+ * Partially update content
374
+ */
375
+ async patchContent(id, data) {
376
+ return this.patch(`/content/${id}`, data);
377
+ }
378
+ /**
379
+ * Delete content
380
+ */
381
+ async deleteContent(id) {
382
+ return this.delete(`/content/${id}`);
383
+ }
384
+ /**
385
+ * Publish content
386
+ */
387
+ async publishContent(id) {
388
+ return this.create(`/content/${id}/publish`, { action: "publish" });
389
+ }
390
+ /**
391
+ * Unpublish content
392
+ */
393
+ async unpublishContent(id) {
394
+ return this.create(`/content/${id}/publish`, { action: "unpublish" });
395
+ }
396
+ /**
397
+ * Move content to trash
398
+ */
399
+ async trashContent(id) {
400
+ return this.create(`/content/${id}/publish`, { action: "trash" });
401
+ }
402
+ /**
403
+ * Restore content from trash
404
+ */
405
+ async restoreContent(id) {
406
+ return this.create(`/content/${id}/publish`, { action: "restore" });
407
+ }
408
+ /**
409
+ * Get content revisions
410
+ */
411
+ async getContentRevisions(id) {
412
+ return this.getSingle(`/content/${id}/revisions`);
413
+ }
414
+ /**
415
+ * Duplicate content
416
+ */
417
+ async duplicateContent(id) {
418
+ return this.create(`/content/${id}/duplicate`, {});
419
+ }
420
+ };
421
+
422
+ // src/client/api-keys-client.ts
423
+ var ApiKeysClient = class extends BaseClient {
424
+ constructor(http) {
425
+ super(http, "/api/v1");
426
+ }
427
+ /**
428
+ * Get all API keys
429
+ */
430
+ async getApiKeys(params) {
431
+ return this.getPaginated("/keys", params);
432
+ }
433
+ /**
434
+ * Get API key by ID
435
+ */
436
+ async getApiKeyById(id) {
437
+ return this.getSingle(`/keys/${id}`);
438
+ }
439
+ /**
440
+ * Create new API key
441
+ */
442
+ async createApiKey(data) {
443
+ return this.create("/keys", data);
444
+ }
445
+ /**
446
+ * Update API key
447
+ */
448
+ async updateApiKey(id, data) {
449
+ return this.update(`/keys/${id}`, data);
450
+ }
451
+ /**
452
+ * Delete API key
453
+ */
454
+ async deleteApiKey(id) {
455
+ return this.delete(`/keys/${id}`);
456
+ }
457
+ /**
458
+ * Regenerate API key
459
+ */
460
+ async regenerateApiKey(id) {
461
+ return this.create(`/keys/${id}/regenerate`, {});
462
+ }
463
+ /**
464
+ * Activate API key
465
+ */
466
+ async activateApiKey(id) {
467
+ return this.patch(`/keys/${id}`, { isActive: true });
468
+ }
469
+ /**
470
+ * Deactivate API key
471
+ */
472
+ async deactivateApiKey(id) {
473
+ return this.patch(`/keys/${id}`, { isActive: false });
474
+ }
475
+ /**
476
+ * Get API key usage statistics
477
+ */
478
+ async getApiKeyStats(id) {
479
+ return this.getSingle(`/keys/${id}/stats`);
480
+ }
481
+ /**
482
+ * Test API key validity
483
+ */
484
+ async testApiKey(key) {
485
+ return this.create("/keys/test", { key });
486
+ }
487
+ };
488
+
489
+ // src/client/organizations-client.ts
490
+ var OrganizationsClient = class extends BaseClient {
491
+ constructor(http) {
492
+ super(http, "/api/v1");
493
+ }
494
+ /**
495
+ * Get all organizations
496
+ */
497
+ async getOrganizations(params) {
498
+ return this.getPaginated("/organizations", params);
499
+ }
500
+ /**
501
+ * Get organization by ID
502
+ */
503
+ async getOrganizationById(id) {
504
+ return this.getSingle(`/organizations/${id}`);
505
+ }
506
+ /**
507
+ * Create new organization
508
+ */
509
+ async createOrganization(data) {
510
+ return this.create("/organizations", data);
511
+ }
512
+ /**
513
+ * Update organization
514
+ */
515
+ async updateOrganization(id, data) {
516
+ return this.update(`/organizations/${id}`, data);
517
+ }
518
+ /**
519
+ * Delete organization
520
+ */
521
+ async deleteOrganization(id) {
522
+ return this.delete(`/organizations/${id}`);
523
+ }
524
+ /**
525
+ * Get organization members
526
+ */
527
+ async getOrganizationMembers(id) {
528
+ return this.getSingle(`/organizations/${id}/members`);
529
+ }
530
+ /**
531
+ * Add member to organization
532
+ */
533
+ async addOrganizationMember(id, data) {
534
+ return this.create(`/organizations/${id}/members`, data);
535
+ }
536
+ /**
537
+ * Update organization member role
538
+ */
539
+ async updateOrganizationMember(id, userId, data) {
540
+ return this.update(`/organizations/${id}/members/${userId}`, data);
541
+ }
542
+ /**
543
+ * Remove member from organization
544
+ */
545
+ async removeOrganizationMember(id, userId) {
546
+ return this.delete(`/organizations/${id}/members/${userId}`);
547
+ }
548
+ /**
549
+ * Get organization settings
550
+ */
551
+ async getOrganizationSettings(id) {
552
+ return this.getSingle(`/organizations/${id}/settings`);
553
+ }
554
+ /**
555
+ * Update organization settings
556
+ */
557
+ async updateOrganizationSettings(id, settings) {
558
+ return this.update(`/organizations/${id}/settings`, settings);
559
+ }
560
+ };
561
+
562
+ // src/client/sites-client.ts
563
+ var SitesClient = class extends BaseClient {
564
+ constructor(http) {
565
+ super(http, "/api/v1");
566
+ }
567
+ /**
568
+ * Get all sites
569
+ */
570
+ async getSites(params) {
571
+ return this.getPaginated("/sites", params);
572
+ }
573
+ /**
574
+ * Get site by ID
575
+ */
576
+ async getSiteById(id) {
577
+ return this.getSingle(`/sites/${id}`);
578
+ }
579
+ /**
580
+ * Get site by name
581
+ */
582
+ async getSiteByName(name) {
583
+ return this.getSingle(`/sites/name/${name}`);
584
+ }
585
+ /**
586
+ * Create new site
587
+ */
588
+ async createSite(data) {
589
+ return this.create("/sites", data);
590
+ }
591
+ /**
592
+ * Update site
593
+ */
594
+ async updateSite(id, data) {
595
+ return this.update(`/sites/${id}`, data);
596
+ }
597
+ /**
598
+ * Delete site
599
+ */
600
+ async deleteSite(id) {
601
+ return this.delete(`/sites/${id}`);
602
+ }
603
+ /**
604
+ * Activate site
605
+ */
606
+ async activateSite(id) {
607
+ return this.patch(`/sites/${id}`, { isActive: true });
608
+ }
609
+ /**
610
+ * Deactivate site
611
+ */
612
+ async deactivateSite(id) {
613
+ return this.patch(`/sites/${id}`, { isActive: false });
614
+ }
615
+ /**
616
+ * Get site configuration
617
+ */
618
+ async getSiteConfig(id) {
619
+ return this.getSingle(`/sites/${id}/config`);
620
+ }
621
+ /**
622
+ * Update site configuration
623
+ */
624
+ async updateSiteConfig(id, config) {
625
+ return this.update(`/sites/${id}/config`, config);
626
+ }
627
+ /**
628
+ * Get site analytics
629
+ */
630
+ async getSiteAnalytics(id, params) {
631
+ return this.http.get(`/sites/${id}/analytics`, params);
632
+ }
633
+ /**
634
+ * Get site domains
635
+ */
636
+ async getSiteDomains(id) {
637
+ return this.getSingle(`/sites/${id}/domains`);
638
+ }
639
+ /**
640
+ * Add domain to site
641
+ */
642
+ async addSiteDomain(id, data) {
643
+ return this.create(`/sites/${id}/domains`, data);
644
+ }
645
+ /**
646
+ * Remove domain from site
647
+ */
648
+ async removeSiteDomain(id, domain) {
649
+ return this.delete(`/sites/${id}/domains/${encodeURIComponent(domain)}`);
650
+ }
651
+ /**
652
+ * Verify domain ownership
653
+ */
654
+ async verifySiteDomain(id, domain) {
655
+ return this.create(`/sites/${id}/domains/${encodeURIComponent(domain)}/verify`, {});
656
+ }
657
+ };
658
+
659
+ // src/client/products-client.ts
660
+ var ProductsClient = class extends BaseClient {
661
+ constructor(http) {
662
+ super(http, "/api/v1");
663
+ }
664
+ /**
665
+ * Get all products for a site
666
+ */
667
+ async getProducts(siteName, params) {
668
+ const normalizeList = (value) => {
669
+ if (value === void 0 || value === null) {
670
+ return void 0;
671
+ }
672
+ const values = Array.isArray(value) ? value : [value];
673
+ const normalized = values.map((item) => String(item).trim()).filter((item) => item.length > 0);
674
+ return normalized.length > 0 ? normalized.join(",") : void 0;
675
+ };
676
+ const normalizedParams = params ? { ...params } : void 0;
677
+ if (normalizedParams) {
678
+ const normalizedCategories = normalizeList(normalizedParams.category);
679
+ if (normalizedCategories !== void 0) {
680
+ normalizedParams.category = normalizedCategories;
681
+ } else {
682
+ delete normalizedParams.category;
683
+ }
684
+ const normalizedCategoryIds = normalizeList(normalizedParams.category_id);
685
+ if (normalizedCategoryIds !== void 0) {
686
+ normalizedParams.category_id = normalizedCategoryIds;
687
+ } else {
688
+ delete normalizedParams.category_id;
689
+ }
690
+ }
691
+ return this.http.get(this.buildPath(this.siteScopedEndpoint(siteName, "/products")), normalizedParams);
692
+ }
693
+ /**
694
+ * Get product by ID
695
+ */
696
+ async getProductById(id) {
697
+ return this.getSingle(`/products/${id}`);
698
+ }
699
+ /**
700
+ * Get product by SKU
701
+ */
702
+ async getProductBySku(sku) {
703
+ return this.getSingle(`/products/sku/${sku}`);
704
+ }
705
+ /**
706
+ * Get product by slug and site name
707
+ */
708
+ async getProductBySlug(siteName, slug) {
709
+ return this.http.get(this.buildPath(
710
+ this.siteScopedEndpoint(siteName, `/products/slug/${encodeURIComponent(slug)}`)
711
+ ));
712
+ }
713
+ /**
714
+ * Create new product
715
+ */
716
+ async createProduct(data) {
717
+ return this.create("/products", data);
718
+ }
719
+ /**
720
+ * Update product
721
+ */
722
+ async updateProduct(id, data) {
723
+ return this.update(`/products/${id}`, data);
724
+ }
725
+ /**
726
+ * Delete product
727
+ */
728
+ async deleteProduct(id) {
729
+ return this.delete(`/products/${id}`);
730
+ }
731
+ /**
732
+ * Activate product
733
+ */
734
+ async activateProduct(id) {
735
+ return this.patch(`/products/${id}`, { isActive: true });
736
+ }
737
+ /**
738
+ * Deactivate product
739
+ */
740
+ async deactivateProduct(id) {
741
+ return this.patch(`/products/${id}`, { isActive: false });
742
+ }
743
+ /**
744
+ * Get product variants
745
+ */
746
+ async getProductVariants(id) {
747
+ return this.getSingle(`/products/${id}/variants`);
748
+ }
749
+ /**
750
+ * Add product variant
751
+ */
752
+ async addProductVariant(id, data) {
753
+ return this.create(`/products/${id}/variants`, data);
754
+ }
755
+ /**
756
+ * Update product variant
757
+ */
758
+ async updateProductVariant(id, variantId, data) {
759
+ return this.update(`/products/${id}/variants/${variantId}`, data);
760
+ }
761
+ /**
762
+ * Delete product variant
763
+ */
764
+ async deleteProductVariant(id, variantId) {
765
+ return this.delete(`/products/${id}/variants/${variantId}`);
766
+ }
767
+ /**
768
+ * Get product inventory
769
+ */
770
+ async getProductInventory(id) {
771
+ return this.getSingle(`/products/${id}/inventory`);
772
+ }
773
+ /**
774
+ * Update product inventory
775
+ */
776
+ async updateProductInventory(id, data) {
777
+ return this.create(`/products/${id}/inventory`, data);
778
+ }
779
+ /**
780
+ * Get product categories
781
+ */
782
+ async getProductCategories(id) {
783
+ return this.getSingle(`/products/${id}/categories`);
784
+ }
785
+ /**
786
+ * Add product to category
787
+ */
788
+ async addProductToCategory(id, categoryId) {
789
+ return this.create(`/products/${id}/categories`, { categoryId });
790
+ }
791
+ /**
792
+ * Remove product from category
793
+ */
794
+ async removeProductFromCategory(id, categoryId) {
795
+ return this.delete(`/products/${id}/categories/${categoryId}`);
796
+ }
797
+ /**
798
+ * Get products by category slug
799
+ */
800
+ async getProductsByCategorySlug(siteName, categorySlug, params) {
801
+ const queryParams = params ? {
802
+ limit: params.limit,
803
+ offset: params.page ? (params.page - 1) * (params.limit || 20) : void 0,
804
+ published: params.published,
805
+ search: params.search
806
+ } : void 0;
807
+ return this.http.get(this.buildPath(
808
+ this.siteScopedEndpoint(siteName, `/products/category/${encodeURIComponent(categorySlug)}`)
809
+ ), queryParams);
810
+ }
811
+ };
812
+
813
+ // src/client/categories-client.ts
814
+ var CategoriesClient = class extends BaseClient {
815
+ constructor(http) {
816
+ super(http, "/api/v1");
817
+ }
818
+ /**
819
+ * Get all categories
820
+ */
821
+ async getCategories(params) {
822
+ return this.getPaginated("/categories", params);
823
+ }
824
+ /**
825
+ * Get category by ID
826
+ */
827
+ async getCategoryById(id) {
828
+ return this.getSingle(`/categories/${id}`);
829
+ }
830
+ /**
831
+ * Get category by slug
832
+ */
833
+ async getCategoryBySlug(slug) {
834
+ return this.getSingle(`/categories/slug/${slug}`);
835
+ }
836
+ /**
837
+ * Get product category by slug (for products)
838
+ */
839
+ async getProductCategoryBySlug(siteName, slug) {
840
+ return this.http.get(this.buildPath(
841
+ this.siteScopedEndpoint(siteName, `/product_category/slug/${encodeURIComponent(slug)}`)
842
+ ));
843
+ }
844
+ /**
845
+ * Create new category
846
+ */
847
+ async createCategory(data) {
848
+ return this.create("/categories", data);
849
+ }
850
+ /**
851
+ * Update category
852
+ */
853
+ async updateCategory(id, data) {
854
+ return this.update(`/categories/${id}`, data);
855
+ }
856
+ /**
857
+ * Delete category
858
+ */
859
+ async deleteCategory(id) {
860
+ return this.delete(`/categories/${id}`);
861
+ }
862
+ /**
863
+ * Get category tree (hierarchical structure)
864
+ */
865
+ async getCategoryTree(rootId) {
866
+ const endpoint = rootId ? `/categories/tree/${rootId}` : "/categories/tree";
867
+ return this.getSingle(endpoint);
868
+ }
869
+ /**
870
+ * Get category children
871
+ */
872
+ async getCategoryChildren(id) {
873
+ return this.getSingle(`/categories/${id}/children`);
874
+ }
875
+ /**
876
+ * Get category parent
877
+ */
878
+ async getCategoryParent(id) {
879
+ return this.getSingle(`/categories/${id}/parent`);
880
+ }
881
+ /**
882
+ * Move category to new parent
883
+ */
884
+ async moveCategoryToParent(id, parentId) {
885
+ return this.patch(`/categories/${id}`, { parentId });
886
+ }
887
+ /**
888
+ * Get category breadcrumb path
889
+ */
890
+ async getCategoryBreadcrumb(id) {
891
+ return this.getSingle(`/categories/${id}/breadcrumb`);
892
+ }
893
+ /**
894
+ * Get category content/products
895
+ */
896
+ async getCategoryContent(id, params) {
897
+ return this.http.get(`/categories/${id}/content`, params);
898
+ }
899
+ /**
900
+ * Bulk update category order
901
+ */
902
+ async updateCategoryOrder(updates) {
903
+ return this.create("/categories/reorder", { updates });
904
+ }
905
+ /**
906
+ * Search categories
907
+ */
908
+ async searchCategories(query, params) {
909
+ return this.http.get(`/categories/search`, { q: query, ...params });
910
+ }
911
+ };
912
+
913
+ // src/client/webhooks-client.ts
914
+ var WebhooksClient = class extends BaseClient {
915
+ constructor(http) {
916
+ super(http, "/api/v1");
917
+ }
918
+ /**
919
+ * Get all webhooks
920
+ */
921
+ async getWebhooks(params) {
922
+ return this.getPaginated("/webhooks", params);
923
+ }
924
+ /**
925
+ * Get webhook by ID
926
+ */
927
+ async getWebhookById(id) {
928
+ return this.getSingle(`/webhooks/${id}`);
929
+ }
930
+ /**
931
+ * Create new webhook
932
+ */
933
+ async createWebhook(data) {
934
+ return this.create("/webhooks", data);
935
+ }
936
+ /**
937
+ * Update webhook
938
+ */
939
+ async updateWebhook(id, data) {
940
+ return this.update(`/webhooks/${id}`, data);
941
+ }
942
+ /**
943
+ * Delete webhook
944
+ */
945
+ async deleteWebhook(id) {
946
+ return this.delete(`/webhooks/${id}`);
947
+ }
948
+ /**
949
+ * Activate webhook
950
+ */
951
+ async activateWebhook(id) {
952
+ return this.patch(`/webhooks/${id}`, { isActive: true });
953
+ }
954
+ /**
955
+ * Deactivate webhook
956
+ */
957
+ async deactivateWebhook(id) {
958
+ return this.patch(`/webhooks/${id}`, { isActive: false });
959
+ }
960
+ /**
961
+ * Test webhook
962
+ */
963
+ async testWebhook(id, data) {
964
+ return this.create(`/webhooks/${id}/test`, data || {});
965
+ }
966
+ /**
967
+ * Get webhook events
968
+ */
969
+ async getWebhookEvents(id, params) {
970
+ return this.getPaginated(`/webhooks/${id}/events`, params);
971
+ }
972
+ /**
973
+ * Get webhook logs
974
+ */
975
+ async getWebhookLogs(id, params) {
976
+ return this.getPaginated(`/webhooks/${id}/logs`, params);
977
+ }
978
+ /**
979
+ * Get webhook statistics
980
+ */
981
+ async getWebhookStats(id) {
982
+ const endpoint = id ? `/webhooks/${id}/stats` : "/webhooks/stats";
983
+ return this.getSingle(endpoint);
984
+ }
985
+ /**
986
+ * Get supported webhook providers
987
+ */
988
+ async getWebhookProviders() {
989
+ return this.getSingle("/webhooks/providers");
990
+ }
991
+ /**
992
+ * Retry failed webhook event
993
+ */
994
+ async retryWebhookEvent(webhookId, eventId) {
995
+ return this.create(`/webhooks/${webhookId}/events/${eventId}/retry`, {});
996
+ }
997
+ /**
998
+ * Bulk retry failed webhook events
999
+ */
1000
+ async bulkRetryWebhookEvents(webhookId, eventIds) {
1001
+ return this.create(`/webhooks/${webhookId}/events/bulk-retry`, { eventIds });
1002
+ }
1003
+ /**
1004
+ * Get webhook delivery attempts
1005
+ */
1006
+ async getWebhookDeliveryAttempts(webhookId, eventId) {
1007
+ return this.getSingle(`/webhooks/${webhookId}/events/${eventId}/attempts`);
1008
+ }
1009
+ };
1010
+
1011
+ // src/client/checkout-client.ts
1012
+ var CheckoutClient = class extends BaseClient {
1013
+ constructor(http) {
1014
+ super(http, "/api/v1");
1015
+ }
1016
+ /**
1017
+ * Get CSRF token for a specific site
1018
+ * @param siteName - The site name to get CSRF token for
1019
+ */
1020
+ async getCsrfToken(siteName) {
1021
+ return this.http.get(`/api/v1/csrf/token/${siteName}`);
1022
+ }
1023
+ /**
1024
+ * Create Stripe checkout session with CSRF protection
1025
+ * @param siteName - The site name for the checkout session
1026
+ * @param data - Checkout session data (can include priceId for single item or line_items for multiple)
1027
+ * @param csrfToken - Optional CSRF token (if not provided, will fetch automatically)
1028
+ */
1029
+ async createCheckoutSession(siteName, data, csrfToken) {
1030
+ let token = csrfToken;
1031
+ if (!token) {
1032
+ const csrfResponse = await this.getCsrfToken(siteName);
1033
+ const csrfToken2 = csrfResponse.data?.csrf_token;
1034
+ if (!csrfToken2) {
1035
+ throw new Error("Failed to obtain CSRF token");
1036
+ }
1037
+ token = csrfToken2;
1038
+ }
1039
+ return this.http.request(this.buildPath(
1040
+ this.siteScopedEndpoint(siteName, "/checkout/create-session")
1041
+ ), {
1042
+ method: "POST",
1043
+ body: data,
1044
+ headers: {
1045
+ "X-CSRF-Token": token
1046
+ }
1047
+ });
1048
+ }
1049
+ /**
1050
+ * Get checkout session by ID
1051
+ */
1052
+ async getCheckoutSession(sessionId) {
1053
+ return this.getSingle(`/checkout/sessions/${sessionId}`);
1054
+ }
1055
+ /**
1056
+ * Get checkout sessions for organization
1057
+ */
1058
+ async getCheckoutSessions(params) {
1059
+ return this.getPaginated("/checkout/sessions", params);
1060
+ }
1061
+ /**
1062
+ * Cancel checkout session
1063
+ */
1064
+ async cancelCheckoutSession(sessionId) {
1065
+ return this.create(`/checkout/sessions/${sessionId}/cancel`, {});
1066
+ }
1067
+ /**
1068
+ * Expire checkout session
1069
+ */
1070
+ async expireCheckoutSession(sessionId) {
1071
+ return this.create(`/checkout/sessions/${sessionId}/expire`, {});
1072
+ }
1073
+ /**
1074
+ * Get Stripe publishable key
1075
+ */
1076
+ async getStripePublishableKey() {
1077
+ return this.getSingle("/checkout/stripe/config");
1078
+ }
1079
+ /**
1080
+ * Create payment intent (for custom checkout flows)
1081
+ */
1082
+ async createPaymentIntent(data) {
1083
+ return this.create("/checkout/payment-intent", data);
1084
+ }
1085
+ /**
1086
+ * Confirm payment intent
1087
+ */
1088
+ async confirmPaymentIntent(paymentIntentId, data) {
1089
+ return this.create(`/checkout/payment-intent/${paymentIntentId}/confirm`, data);
1090
+ }
1091
+ /**
1092
+ * Get payment methods for customer
1093
+ */
1094
+ async getPaymentMethods(customerId) {
1095
+ return this.getSingle(`/checkout/customers/${customerId}/payment-methods`);
1096
+ }
1097
+ /**
1098
+ * Create customer
1099
+ */
1100
+ async createCustomer(data) {
1101
+ return this.create("/checkout/customers", data);
1102
+ }
1103
+ /**
1104
+ * Get customer by ID
1105
+ */
1106
+ async getCustomer(customerId) {
1107
+ return this.getSingle(`/checkout/customers/${customerId}`);
1108
+ }
1109
+ /**
1110
+ * Update customer
1111
+ */
1112
+ async updateCustomer(customerId, data) {
1113
+ return this.update(`/checkout/customers/${customerId}`, data);
1114
+ }
1115
+ /**
1116
+ * Get invoices for customer
1117
+ */
1118
+ async getCustomerInvoices(customerId, params) {
1119
+ return this.getPaginated(`/checkout/customers/${customerId}/invoices`, params);
1120
+ }
1121
+ };
1122
+
1123
+ // src/client/contact-client.ts
1124
+ var ContactClient = class extends BaseClient {
1125
+ constructor(http) {
1126
+ super(http, "/api/v1");
1127
+ }
1128
+ /**
1129
+ * Build a contact endpoint scoped to a site (without /sites prefix)
1130
+ */
1131
+ contactEndpoint(siteName, endpoint) {
1132
+ return this.siteScopedEndpoint(siteName, endpoint, { includeSitesSegment: false });
1133
+ }
1134
+ /**
1135
+ * Submit contact form
1136
+ */
1137
+ async submitContact(siteName, data) {
1138
+ return this.create(this.contactEndpoint(siteName, "/contact/submit"), data);
1139
+ }
1140
+ /**
1141
+ * Get contact submission status
1142
+ */
1143
+ async getContactStatus(siteName, id) {
1144
+ return this.getSingle(this.contactEndpoint(siteName, `/contact/status/${encodeURIComponent(id)}`));
1145
+ }
1146
+ /**
1147
+ * Get all contact submissions (admin only)
1148
+ */
1149
+ async getContactSubmissions(siteName, params) {
1150
+ return this.getPaginated(this.contactEndpoint(siteName, "/contact/list"), params);
1151
+ }
1152
+ /**
1153
+ * Get contact submission by ID (admin only)
1154
+ */
1155
+ async getContactSubmissionById(siteName, id) {
1156
+ return this.getSingle(this.contactEndpoint(siteName, `/contact/${encodeURIComponent(id)}`));
1157
+ }
1158
+ /**
1159
+ * Update contact submission status (admin only)
1160
+ */
1161
+ async updateContactStatus(siteName, id, status, notes) {
1162
+ return this.patch(this.contactEndpoint(siteName, `/contact/${encodeURIComponent(id)}`), { status, notes });
1163
+ }
1164
+ /**
1165
+ * Mark contact as read (admin only)
1166
+ */
1167
+ async markContactAsRead(siteName, id) {
1168
+ return this.patch(this.contactEndpoint(siteName, `/contact/${encodeURIComponent(id)}`), { status: "read" });
1169
+ }
1170
+ /**
1171
+ * Mark contact as unread (admin only)
1172
+ */
1173
+ async markContactAsUnread(siteName, id) {
1174
+ return this.patch(this.contactEndpoint(siteName, `/contact/${encodeURIComponent(id)}`), { status: "unread" });
1175
+ }
1176
+ /**
1177
+ * Archive contact submission (admin only)
1178
+ */
1179
+ async archiveContact(siteName, id) {
1180
+ return this.patch(this.contactEndpoint(siteName, `/contact/${encodeURIComponent(id)}`), { status: "archived" });
1181
+ }
1182
+ /**
1183
+ * Delete contact submission (admin only)
1184
+ */
1185
+ async deleteContact(siteName, id) {
1186
+ return this.delete(this.contactEndpoint(siteName, `/contact/${encodeURIComponent(id)}`));
1187
+ }
1188
+ /**
1189
+ * Bulk update contact submissions (admin only)
1190
+ */
1191
+ async bulkUpdateContacts(siteName, data) {
1192
+ return this.create(this.contactEndpoint(siteName, "/contact/bulk-update"), data);
1193
+ }
1194
+ /**
1195
+ * Get contact form statistics (admin only)
1196
+ */
1197
+ async getContactStats(siteName, params) {
1198
+ return this.http.get(this.buildPath(this.contactEndpoint(siteName, "/contact/stats")), params);
1199
+ }
1200
+ /**
1201
+ * Export contact submissions (admin only)
1202
+ */
1203
+ async exportContacts(siteName, params) {
1204
+ return this.create(this.contactEndpoint(siteName, "/contact/export"), params || {});
1205
+ }
1206
+ /**
1207
+ * Get contact form configuration
1208
+ */
1209
+ async getContactConfig(siteName) {
1210
+ return this.getSingle(this.contactEndpoint(siteName, "/contact/config"));
1211
+ }
1212
+ /**
1213
+ * Update contact form configuration (admin only)
1214
+ */
1215
+ async updateContactConfig(siteName, data) {
1216
+ return this.update(this.contactEndpoint(siteName, "/contact/config"), data);
1217
+ }
1218
+ };
1219
+
1220
+ // src/perspect-api-client.ts
1221
+ var PerspectApiClient = class {
1222
+ http;
1223
+ // Service clients
1224
+ auth;
1225
+ content;
1226
+ apiKeys;
1227
+ organizations;
1228
+ sites;
1229
+ products;
1230
+ categories;
1231
+ webhooks;
1232
+ checkout;
1233
+ contact;
1234
+ constructor(config) {
1235
+ if (!config.baseUrl) {
1236
+ throw new Error("baseUrl is required in PerspectApiConfig");
1237
+ }
1238
+ this.http = new HttpClient(config);
1239
+ this.auth = new AuthClient(this.http);
1240
+ this.content = new ContentClient(this.http);
1241
+ this.apiKeys = new ApiKeysClient(this.http);
1242
+ this.organizations = new OrganizationsClient(this.http);
1243
+ this.sites = new SitesClient(this.http);
1244
+ this.products = new ProductsClient(this.http);
1245
+ this.categories = new CategoriesClient(this.http);
1246
+ this.webhooks = new WebhooksClient(this.http);
1247
+ this.checkout = new CheckoutClient(this.http);
1248
+ this.contact = new ContactClient(this.http);
1249
+ }
1250
+ /**
1251
+ * Update authentication token
1252
+ */
1253
+ setAuth(jwt) {
1254
+ this.http.setAuth(jwt);
1255
+ }
1256
+ /**
1257
+ * Update API key
1258
+ */
1259
+ setApiKey(apiKey) {
1260
+ this.http.setApiKey(apiKey);
1261
+ }
1262
+ /**
1263
+ * Clear authentication
1264
+ */
1265
+ clearAuth() {
1266
+ this.http.clearAuth();
1267
+ }
1268
+ /**
1269
+ * Health check endpoint
1270
+ */
1271
+ async health() {
1272
+ return this.http.get("/health");
1273
+ }
1274
+ /**
1275
+ * Get API version info
1276
+ */
1277
+ async getVersion() {
1278
+ return this.http.get("/api/v1/version");
1279
+ }
1280
+ /**
1281
+ * Get API status and statistics
1282
+ */
1283
+ async getStatus() {
1284
+ return this.http.get("/api/v1/status");
1285
+ }
1286
+ /**
1287
+ * Test API connectivity and authentication
1288
+ */
1289
+ async test() {
1290
+ return this.http.get("/api/v1/test");
1291
+ }
1292
+ /**
1293
+ * Get CSRF token (for form submissions)
1294
+ * @param siteName - Optional site name for site-specific token (recommended for development)
1295
+ */
1296
+ async getCsrfToken(siteName) {
1297
+ if (siteName) {
1298
+ return this.http.get(`/api/v1/csrf/token/${siteName}`);
1299
+ }
1300
+ return this.http.get("/api/v1/csrf/token");
1301
+ }
1302
+ /**
1303
+ * Validate CSRF token
1304
+ */
1305
+ async validateCsrfToken(token) {
1306
+ return this.http.post("/api/v1/csrf/validate", { token });
1307
+ }
1308
+ };
1309
+ function createPerspectApiClient(config) {
1310
+ return new PerspectApiClient(config);
1311
+ }
1312
+ var perspect_api_client_default = PerspectApiClient;
1313
+
1314
+ // src/loaders.ts
1315
+ var noopLogger = {};
1316
+ var log = (logger, level, ...args) => {
1317
+ const target = logger?.[level];
1318
+ if (typeof target === "function") {
1319
+ target(...args);
1320
+ }
1321
+ };
1322
+ var isMediaItem = (value) => {
1323
+ if (!value || typeof value !== "object") {
1324
+ return false;
1325
+ }
1326
+ const media = value;
1327
+ return typeof media.link === "string" && typeof media.media_id === "string";
1328
+ };
1329
+ var normalizeMediaList = (rawMedia) => {
1330
+ if (!Array.isArray(rawMedia)) {
1331
+ return [];
1332
+ }
1333
+ if (rawMedia.length > 0 && Array.isArray(rawMedia[0])) {
1334
+ return rawMedia.flat().filter(isMediaItem);
1335
+ }
1336
+ return rawMedia.filter(isMediaItem);
1337
+ };
1338
+ var normalizeQueryParamList = (value) => {
1339
+ if (value === void 0 || value === null) {
1340
+ return void 0;
1341
+ }
1342
+ const values = Array.isArray(value) ? value : [value];
1343
+ const normalized = values.map((item) => String(item).trim()).filter((item) => item.length > 0);
1344
+ return normalized.length > 0 ? normalized.join(",") : void 0;
1345
+ };
1346
+ var transformProduct = (perspectProduct, logger) => {
1347
+ const rawProduct = perspectProduct ?? {};
1348
+ const productId = rawProduct.product_id ?? rawProduct.id ?? rawProduct.sku ?? "unknown";
1349
+ const productName = rawProduct.product ?? rawProduct.name ?? rawProduct.title ?? "Untitled";
1350
+ const productSlug = rawProduct.slug ?? rawProduct.product_slug ?? (productId ? `product-${productId}` : "product");
1351
+ const slugPrefix = rawProduct.slug_prefix ?? rawProduct.slugPrefix ?? rawProduct.category ?? "artwork";
1352
+ const mediaItems = normalizeMediaList(rawProduct.media);
1353
+ const primaryImage = mediaItems[0]?.link ?? rawProduct.image ?? rawProduct.image_url ?? rawProduct.thumbnail ?? "";
1354
+ log(logger, "debug", "[PerspectAPI] Transform product", {
1355
+ productId,
1356
+ productSlug,
1357
+ mediaCount: mediaItems.length
1358
+ });
1359
+ return {
1360
+ id: typeof productId === "string" ? productId : String(productId),
1361
+ product: productName,
1362
+ name: productName,
1363
+ slug: productSlug,
1364
+ slug_prefix: slugPrefix,
1365
+ price: typeof rawProduct.price === "number" ? rawProduct.price : Number(rawProduct.price ?? 0),
1366
+ currency: rawProduct.currency ?? "USD",
1367
+ description: rawProduct.description ?? "",
1368
+ description_markdown: rawProduct.description_markdown ?? rawProduct.description ?? "",
1369
+ image: primaryImage,
1370
+ media: mediaItems,
1371
+ gateway_product_id_live: rawProduct.gateway_product_id_live,
1372
+ gateway_product_id_test: rawProduct.gateway_product_id_test,
1373
+ stripe_product_id_live: rawProduct.stripe_product_id_live,
1374
+ stripe_product_id_test: rawProduct.stripe_product_id_test,
1375
+ isActive: rawProduct.isActive ?? rawProduct.is_active ?? true,
1376
+ // Preserve original payload for advanced consumers
1377
+ ...rawProduct
1378
+ };
1379
+ };
1380
+ var transformContent = (perspectContent) => {
1381
+ const raw = perspectContent ?? {};
1382
+ return {
1383
+ id: typeof raw.id === "string" ? raw.id : String(raw.id ?? ""),
1384
+ slug: raw.slug ?? `post-${raw.id ?? "unknown"}`,
1385
+ slug_prefix: raw.slug_prefix ?? (raw.pageType === "post" ? "blog" : "page"),
1386
+ page_type: raw.pageType,
1387
+ title: raw.pageTitle,
1388
+ content: raw.pageContent,
1389
+ excerpt: typeof raw.pageContent === "string" ? `${raw.pageContent.slice(0, 200)}...` : void 0,
1390
+ published: raw.pageStatus === "publish",
1391
+ created_at: raw.createdAt,
1392
+ updated_at: raw.updatedAt,
1393
+ description: raw.description,
1394
+ published_date: raw.published_date,
1395
+ author: raw.author,
1396
+ tags: raw.tags,
1397
+ image: raw.image
1398
+ };
1399
+ };
1400
+ var getDefaultFallbackProducts = (siteName) => {
1401
+ const safeSite = siteName.replace(/\s+/g, "-").toLowerCase();
1402
+ const mediaSamples = [
1403
+ {
1404
+ media_id: `${safeSite}-media-1`,
1405
+ attachment_id: 1001,
1406
+ file_name: "sample-artwork-1.jpg",
1407
+ link: "https://picsum.photos/1200/800?random=1",
1408
+ content_type: "image/jpeg",
1409
+ width: 1200,
1410
+ height: 800,
1411
+ filesize: 245760,
1412
+ r2_key: `${safeSite}/artwork-1.jpg`,
1413
+ site_name: siteName
1414
+ },
1415
+ {
1416
+ media_id: `${safeSite}-media-2`,
1417
+ attachment_id: 1002,
1418
+ file_name: "sample-artwork-2.jpg",
1419
+ link: "https://picsum.photos/1200/800?random=2",
1420
+ content_type: "image/jpeg",
1421
+ width: 1200,
1422
+ height: 800,
1423
+ filesize: 312480,
1424
+ r2_key: `${safeSite}/artwork-2.jpg`,
1425
+ site_name: siteName
1426
+ }
1427
+ ];
1428
+ return [
1429
+ {
1430
+ id: `${safeSite}-product-1`,
1431
+ product: `${siteName} Sample Artwork 1`,
1432
+ slug: `${safeSite}-sample-artwork-1`,
1433
+ slug_prefix: "artwork",
1434
+ price: 299,
1435
+ description: `Beautiful artwork from ${siteName}`,
1436
+ description_markdown: `Beautiful artwork from **${siteName}**`,
1437
+ image: "https://picsum.photos/800/600?random=1",
1438
+ media: mediaSamples
1439
+ },
1440
+ {
1441
+ id: `${safeSite}-product-2`,
1442
+ product: `${siteName} Sample Artwork 2`,
1443
+ slug: `${safeSite}-sample-artwork-2`,
1444
+ slug_prefix: "artwork",
1445
+ price: 499,
1446
+ description: `Exquisite piece from ${siteName} collection`,
1447
+ description_markdown: `Exquisite piece from **${siteName}** collection`,
1448
+ image: "https://picsum.photos/800/600?random=2",
1449
+ media: mediaSamples
1450
+ }
1451
+ ];
1452
+ };
1453
+ var getDefaultFallbackPosts = (siteName) => {
1454
+ return [
1455
+ {
1456
+ id: `${siteName}-post-1`,
1457
+ slug: "welcome-post",
1458
+ slug_prefix: "blog",
1459
+ page_type: "post",
1460
+ title: `Welcome to ${siteName}`,
1461
+ content: `<p>Welcome to our ${siteName} blog. Here you'll find articles about art, culture, and our collections.</p>`,
1462
+ excerpt: `Welcome to our ${siteName} blog.`,
1463
+ published: true,
1464
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
1465
+ }
1466
+ ];
1467
+ };
1468
+ var resolveFallbackProducts = ({ siteName, fallbackProducts }) => {
1469
+ if (fallbackProducts && fallbackProducts.length > 0) {
1470
+ return fallbackProducts;
1471
+ }
1472
+ return getDefaultFallbackProducts(siteName);
1473
+ };
1474
+ var resolveFallbackPosts = ({ siteName, fallbackPosts }) => {
1475
+ if (fallbackPosts && fallbackPosts.length > 0) {
1476
+ return fallbackPosts;
1477
+ }
1478
+ return getDefaultFallbackPosts(siteName);
1479
+ };
1480
+ async function loadProducts(options) {
1481
+ const {
1482
+ client,
1483
+ siteName,
1484
+ logger = noopLogger,
1485
+ fallbackProducts,
1486
+ limit = 100,
1487
+ offset,
1488
+ search,
1489
+ category,
1490
+ categoryIds
1491
+ } = options;
1492
+ if (!client) {
1493
+ log(logger, "warn", "[PerspectAPI] No client configured, using fallback products");
1494
+ return resolveFallbackProducts({ siteName, fallbackProducts });
1495
+ }
1496
+ try {
1497
+ log(logger, "info", `[PerspectAPI] Loading products for site "${siteName}"`);
1498
+ const queryParams = {
1499
+ isActive: true,
1500
+ limit,
1501
+ offset,
1502
+ search
1503
+ };
1504
+ const normalizedCategory = normalizeQueryParamList(category);
1505
+ if (normalizedCategory) {
1506
+ queryParams.category = normalizedCategory;
1507
+ }
1508
+ const normalizedCategoryIds = normalizeQueryParamList(categoryIds);
1509
+ if (normalizedCategoryIds) {
1510
+ queryParams.category_id = normalizedCategoryIds;
1511
+ }
1512
+ const response = await client.products.getProducts(siteName, queryParams);
1513
+ if (!response.data) {
1514
+ log(logger, "warn", "[PerspectAPI] Products response missing data, returning fallback set");
1515
+ return resolveFallbackProducts({ siteName, fallbackProducts });
1516
+ }
1517
+ log(logger, "debug", `[PerspectAPI] Found ${response.data.length} products, transforming...`);
1518
+ return response.data.map((product) => transformProduct(product, logger));
1519
+ } catch (error) {
1520
+ log(logger, "error", "[PerspectAPI] Error loading products", error);
1521
+ return resolveFallbackProducts({ siteName, fallbackProducts });
1522
+ }
1523
+ }
1524
+ async function loadProductBySlug(options) {
1525
+ const {
1526
+ client,
1527
+ siteName,
1528
+ slug,
1529
+ logger = noopLogger,
1530
+ fallbackProducts
1531
+ } = options;
1532
+ if (!client) {
1533
+ log(logger, "warn", "[PerspectAPI] No client configured, searching fallback products");
1534
+ const fallback = resolveFallbackProducts({ siteName, fallbackProducts });
1535
+ return fallback.find((product) => product.slug === slug) ?? null;
1536
+ }
1537
+ try {
1538
+ log(logger, "info", `[PerspectAPI] Loading product "${slug}" for site "${siteName}"`);
1539
+ const response = await client.products.getProductBySlug(siteName, slug);
1540
+ if (!response.data) {
1541
+ log(logger, "warn", `[PerspectAPI] Product not found for slug "${slug}"`);
1542
+ return null;
1543
+ }
1544
+ return transformProduct(response.data, logger);
1545
+ } catch (error) {
1546
+ log(logger, "error", `[PerspectAPI] Error loading product slug "${slug}"`, error);
1547
+ return null;
1548
+ }
1549
+ }
1550
+ async function loadPages(options) {
1551
+ const { client, siteName, logger = noopLogger, params, fallbackPosts } = options;
1552
+ if (!client) {
1553
+ log(logger, "warn", "[PerspectAPI] No client configured, using fallback posts for pages");
1554
+ return resolveFallbackPosts({ siteName, fallbackPosts }).filter(
1555
+ (post) => post.page_type !== "post"
1556
+ );
1557
+ }
1558
+ try {
1559
+ log(logger, "info", `[PerspectAPI] Loading pages for site "${siteName}"`);
1560
+ const response = await client.content.getContent(
1561
+ siteName,
1562
+ {
1563
+ ...params,
1564
+ page_status: params?.page_status ?? "publish",
1565
+ page_type: params?.page_type ?? "page",
1566
+ limit: options.limit ?? params?.limit ?? 100
1567
+ }
1568
+ );
1569
+ if (!response.data) {
1570
+ return [];
1571
+ }
1572
+ return response.data.map((content) => transformContent(content));
1573
+ } catch (error) {
1574
+ log(logger, "error", "[PerspectAPI] Error loading pages", error);
1575
+ return [];
1576
+ }
1577
+ }
1578
+ async function loadPosts(options) {
1579
+ const { client, siteName, logger = noopLogger, params, fallbackPosts } = options;
1580
+ if (!client) {
1581
+ log(logger, "warn", "[PerspectAPI] No client configured, using fallback posts");
1582
+ return resolveFallbackPosts({ siteName, fallbackPosts });
1583
+ }
1584
+ try {
1585
+ log(logger, "info", `[PerspectAPI] Loading posts for site "${siteName}"`);
1586
+ const response = await client.content.getContent(
1587
+ siteName,
1588
+ {
1589
+ ...params,
1590
+ page_status: params?.page_status ?? "publish",
1591
+ page_type: params?.page_type ?? "post",
1592
+ limit: options.limit ?? params?.limit ?? 100
1593
+ }
1594
+ );
1595
+ if (!response.data) {
1596
+ log(logger, "warn", "[PerspectAPI] Posts response missing data");
1597
+ return [];
1598
+ }
1599
+ return response.data.map((content) => transformContent(content));
1600
+ } catch (error) {
1601
+ log(logger, "error", "[PerspectAPI] Error loading posts", error);
1602
+ return [];
1603
+ }
1604
+ }
1605
+ async function loadContentBySlug(options) {
1606
+ const {
1607
+ client,
1608
+ siteName,
1609
+ slug,
1610
+ logger = noopLogger,
1611
+ fallbackPosts
1612
+ } = options;
1613
+ if (!client) {
1614
+ log(logger, "warn", "[PerspectAPI] No client configured, searching fallback posts");
1615
+ const fallback = resolveFallbackPosts({ siteName, fallbackPosts });
1616
+ return fallback.find((post) => post.slug === slug) ?? null;
1617
+ }
1618
+ try {
1619
+ log(logger, "info", `[PerspectAPI] Loading content slug "${slug}" for site "${siteName}"`);
1620
+ const response = await client.content.getContentBySlug(siteName, slug);
1621
+ if (!response.data) {
1622
+ log(logger, "warn", `[PerspectAPI] Content not found for slug "${slug}"`);
1623
+ return null;
1624
+ }
1625
+ return transformContent(response.data);
1626
+ } catch (error) {
1627
+ log(logger, "error", `[PerspectAPI] Error loading content slug "${slug}"`, error);
1628
+ return null;
1629
+ }
1630
+ }
1631
+ async function loadAllContent(options) {
1632
+ const { logger = noopLogger } = options;
1633
+ const [pages, posts] = await Promise.all([
1634
+ loadPages(options),
1635
+ loadPosts(options)
1636
+ ]);
1637
+ log(logger, "debug", `[PerspectAPI] Loaded ${pages.length + posts.length} total content items`);
1638
+ return [...pages, ...posts];
1639
+ }
1640
+ async function createCheckoutSession(options) {
1641
+ const {
1642
+ client,
1643
+ siteName,
1644
+ items,
1645
+ successUrl,
1646
+ cancelUrl,
1647
+ customerEmail,
1648
+ mode = "live",
1649
+ logger = noopLogger,
1650
+ fallbackProducts,
1651
+ metadata,
1652
+ priceIdResolver
1653
+ } = options;
1654
+ if (!client) {
1655
+ log(logger, "error", "[PerspectAPI] Cannot create checkout session without SDK client");
1656
+ return { error: "PerspectAPI client not configured" };
1657
+ }
1658
+ try {
1659
+ const products = await loadProducts({
1660
+ client,
1661
+ siteName,
1662
+ logger,
1663
+ limit: 200,
1664
+ fallbackProducts
1665
+ });
1666
+ const productMap = new Map(
1667
+ products.map((product) => [String(product.id), product])
1668
+ );
1669
+ const line_items = items.map((item) => {
1670
+ const product = productMap.get(String(item.productId));
1671
+ if (!product) {
1672
+ throw new Error(`Product ${item.productId} not found while building checkout session`);
1673
+ }
1674
+ const resolvedPrice = priceIdResolver?.(product, mode) ?? (mode === "test" ? product.stripe_product_id_test ?? product.gateway_product_id_test : product.stripe_product_id_live ?? product.gateway_product_id_live);
1675
+ if (!resolvedPrice) {
1676
+ throw new Error(`Missing Stripe price ID for product ${item.productId} (${product.product ?? product.name ?? "unknown"})`);
1677
+ }
1678
+ return {
1679
+ price: resolvedPrice,
1680
+ quantity: item.quantity
1681
+ };
1682
+ });
1683
+ const checkoutData = {
1684
+ line_items,
1685
+ successUrl,
1686
+ cancelUrl,
1687
+ success_url: successUrl,
1688
+ cancel_url: cancelUrl,
1689
+ customerEmail,
1690
+ customer_email: customerEmail,
1691
+ mode: mode === "test" ? "payment" : "payment",
1692
+ metadata
1693
+ };
1694
+ log(logger, "info", "[PerspectAPI] Creating checkout session");
1695
+ return client.checkout.createCheckoutSession(siteName, checkoutData);
1696
+ } catch (error) {
1697
+ log(logger, "error", "[PerspectAPI] Failed to create checkout session", error);
1698
+ return { error: error instanceof Error ? error.message : "Unknown error creating checkout session" };
1699
+ }
1700
+ }
1701
+ export {
1702
+ ApiKeysClient,
1703
+ AuthClient,
1704
+ BaseClient,
1705
+ CategoriesClient,
1706
+ CheckoutClient,
1707
+ ContactClient,
1708
+ ContentClient,
1709
+ HttpClient,
1710
+ OrganizationsClient,
1711
+ PerspectApiClient,
1712
+ ProductsClient,
1713
+ SitesClient,
1714
+ WebhooksClient,
1715
+ createApiError,
1716
+ createCheckoutSession,
1717
+ createPerspectApiClient,
1718
+ perspect_api_client_default as default,
1719
+ loadAllContent,
1720
+ loadContentBySlug,
1721
+ loadPages,
1722
+ loadPosts,
1723
+ loadProductBySlug,
1724
+ loadProducts,
1725
+ transformContent,
1726
+ transformProduct
1727
+ };