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