perspectapi-ts-sdk 4.1.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,146 @@
1
+ /**
2
+ * v2 Base Client — cursor pagination, expand support, typed errors.
3
+ */
4
+
5
+ import { HttpClient } from '../../utils/http-client';
6
+ import type { V2List, V2Error, V2Deleted } from '../types';
7
+
8
+ export class PerspectV2Error extends Error {
9
+ readonly type: string;
10
+ readonly code: string;
11
+ readonly param?: string;
12
+ readonly status: number;
13
+
14
+ constructor(error: V2Error['error'], status: number) {
15
+ super(error.message);
16
+ this.name = 'PerspectV2Error';
17
+ this.type = error.type;
18
+ this.code = error.code;
19
+ this.param = error.param;
20
+ this.status = status;
21
+ }
22
+ }
23
+
24
+ export abstract class BaseV2Client {
25
+ protected http: HttpClient;
26
+ protected basePath: string;
27
+
28
+ constructor(http: HttpClient, basePath: string) {
29
+ this.http = http;
30
+ this.basePath = basePath;
31
+ }
32
+
33
+ protected buildPath(endpoint: string): string {
34
+ const clean = endpoint.replace(/^\//, '');
35
+ return clean ? `${this.basePath}/${clean}` : this.basePath;
36
+ }
37
+
38
+ protected sitePath(siteName: string, resource: string, suffix = ''): string {
39
+ const encoded = encodeURIComponent(siteName.trim());
40
+ const cleanSuffix = suffix.replace(/^\//, '');
41
+ const end = cleanSuffix ? `/${cleanSuffix}` : '';
42
+ return `/sites/${encoded}/${resource}${end}`;
43
+ }
44
+
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
+ private toParams(params?: any): Record<string, string> | undefined {
47
+ if (!params) return undefined;
48
+ const out: Record<string, string> = {};
49
+ for (const [k, v] of Object.entries(params)) {
50
+ if (v !== undefined && v !== null) out[k] = String(v);
51
+ }
52
+ return Object.keys(out).length > 0 ? out : undefined;
53
+ }
54
+
55
+ /** GET a single resource. */
56
+ protected async getOne<T>(path: string, params?: object): Promise<T> {
57
+ const response = await this.http.get<T>(path, this.toParams(params));
58
+ if (!response.success) {
59
+ throw this.toError(response);
60
+ }
61
+ return response.data!;
62
+ }
63
+
64
+ /** GET a list of resources with cursor pagination. */
65
+ protected async getList<T>(
66
+ path: string,
67
+ params?: object,
68
+ ): Promise<V2List<T>> {
69
+ const response = await this.http.get<V2List<T>>(path, this.toParams(params));
70
+ if (!response.success) {
71
+ throw this.toError(response);
72
+ }
73
+ return response.data!;
74
+ }
75
+
76
+ /** POST to create a resource. */
77
+ protected async post<T>(path: string, body?: unknown): Promise<T> {
78
+ const response = await this.http.post<T>(path, body);
79
+ if (!response.success) {
80
+ throw this.toError(response);
81
+ }
82
+ return response.data!;
83
+ }
84
+
85
+ /** PATCH to update a resource. */
86
+ protected async patchOne<T>(path: string, body?: unknown): Promise<T> {
87
+ const response = await this.http.patch<T>(path, body);
88
+ if (!response.success) {
89
+ throw this.toError(response);
90
+ }
91
+ return response.data!;
92
+ }
93
+
94
+ /** DELETE a resource. */
95
+ protected async deleteOne(path: string): Promise<V2Deleted> {
96
+ const response = await this.http.delete<V2Deleted>(path);
97
+ if (!response.success) {
98
+ throw this.toError(response);
99
+ }
100
+ return response.data!;
101
+ }
102
+
103
+ /**
104
+ * Auto-paginating async generator.
105
+ * Yields every item across all pages.
106
+ *
107
+ * Usage:
108
+ * for await (const item of client.listAutoPaginate(path, params)) { ... }
109
+ */
110
+ protected async *listAutoPaginate<T extends { id: string }>(
111
+ path: string,
112
+ params?: object,
113
+ ): AsyncGenerator<T, void, unknown> {
114
+ let startingAfter: string | undefined;
115
+ let hasMore = true;
116
+
117
+ while (hasMore) {
118
+ const queryParams: Record<string, unknown> = { ...(params as Record<string, unknown>) };
119
+ if (startingAfter) queryParams.starting_after = startingAfter;
120
+
121
+ const page = await this.getList<T>(path, queryParams);
122
+ for (const item of page.data) {
123
+ yield item;
124
+ }
125
+
126
+ hasMore = page.has_more;
127
+ if (page.data.length > 0) {
128
+ startingAfter = page.data[page.data.length - 1].id;
129
+ } else {
130
+ hasMore = false;
131
+ }
132
+ }
133
+ }
134
+
135
+ private toError(response: { data?: unknown; error?: unknown; message?: string }): PerspectV2Error {
136
+ const data = response.data as Record<string, unknown> | undefined;
137
+ const errorObj = (data?.error ?? response.error) as V2Error['error'] | undefined;
138
+ if (errorObj && typeof errorObj === 'object' && 'type' in errorObj) {
139
+ return new PerspectV2Error(errorObj, 400);
140
+ }
141
+ return new PerspectV2Error(
142
+ { type: 'api_error', code: 'unknown', message: response.message ?? 'Unknown error' },
143
+ 500,
144
+ );
145
+ }
146
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * v2 Categories Client
3
+ */
4
+
5
+ import { BaseV2Client } from './base-v2-client';
6
+ import type {
7
+ V2Category, V2CategoryCreateParams, V2CategoryUpdateParams,
8
+ V2PaginationParams, V2List, V2Deleted,
9
+ } from '../types';
10
+
11
+ export class CategoriesV2Client extends BaseV2Client {
12
+
13
+ async list(siteName: string, params?: V2PaginationParams & { type?: string }): Promise<V2List<V2Category>> {
14
+ return this.getList<V2Category>(this.sitePath(siteName, 'categories'), params);
15
+ }
16
+
17
+ async get(siteName: string, id: string): Promise<V2Category> {
18
+ return this.getOne<V2Category>(this.sitePath(siteName, 'categories', id));
19
+ }
20
+
21
+ async create(siteName: string, data: V2CategoryCreateParams): Promise<V2Category> {
22
+ return this.post<V2Category>(this.sitePath(siteName, 'categories'), data);
23
+ }
24
+
25
+ async update(siteName: string, id: string, data: V2CategoryUpdateParams): Promise<V2Category> {
26
+ return this.patchOne<V2Category>(this.sitePath(siteName, 'categories', id), data);
27
+ }
28
+
29
+ async del(siteName: string, id: string): Promise<V2Deleted> {
30
+ return this.deleteOne(this.sitePath(siteName, 'categories', id));
31
+ }
32
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * v2 Collections Client
3
+ */
4
+
5
+ import { BaseV2Client } from './base-v2-client';
6
+ import type {
7
+ V2Collection, V2CollectionItem, V2CollectionCreateParams,
8
+ V2CollectionUpdateParams, V2PaginationParams, V2List, V2Deleted,
9
+ } from '../types';
10
+
11
+ export class CollectionsV2Client extends BaseV2Client {
12
+
13
+ async list(siteName: string, params?: V2PaginationParams): Promise<V2List<V2Collection>> {
14
+ return this.getList<V2Collection>(this.sitePath(siteName, 'collections'), params);
15
+ }
16
+
17
+ async getCurrent(siteName: string): Promise<V2Collection | null> {
18
+ const result = await this.getOne<{ object: string; data: V2Collection | null }>(
19
+ this.sitePath(siteName, 'collections', 'current'),
20
+ );
21
+ return (result as unknown as V2Collection) ?? null;
22
+ }
23
+
24
+ async get(siteName: string, id: string): Promise<V2Collection> {
25
+ return this.getOne<V2Collection>(this.sitePath(siteName, 'collections', id));
26
+ }
27
+
28
+ async create(siteName: string, data: V2CollectionCreateParams): Promise<V2Collection> {
29
+ return this.post<V2Collection>(this.sitePath(siteName, 'collections'), data);
30
+ }
31
+
32
+ async update(siteName: string, id: string, data: V2CollectionUpdateParams): Promise<V2Collection> {
33
+ return this.patchOne<V2Collection>(this.sitePath(siteName, 'collections', id), data);
34
+ }
35
+
36
+ async del(siteName: string, id: string): Promise<V2Deleted> {
37
+ return this.deleteOne(this.sitePath(siteName, 'collections', id));
38
+ }
39
+
40
+ // --- Items ---
41
+
42
+ async listItems(siteName: string, collectionId: string): Promise<V2List<V2CollectionItem>> {
43
+ return this.getList<V2CollectionItem>(
44
+ this.sitePath(siteName, 'collections', `${collectionId}/items`),
45
+ );
46
+ }
47
+
48
+ async addItem(
49
+ siteName: string,
50
+ collectionId: string,
51
+ data: { product_id: string; max_quantity?: number | null; position?: number },
52
+ ): Promise<V2CollectionItem> {
53
+ return this.post<V2CollectionItem>(
54
+ this.sitePath(siteName, 'collections', `${collectionId}/items`),
55
+ data,
56
+ );
57
+ }
58
+
59
+ async removeItem(siteName: string, collectionId: string, itemId: string): Promise<V2Deleted> {
60
+ return this.deleteOne(
61
+ this.sitePath(siteName, 'collections', `${collectionId}/items/${itemId}`),
62
+ );
63
+ }
64
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * v2 Contacts Client
3
+ */
4
+
5
+ import { BaseV2Client } from './base-v2-client';
6
+ import type { V2ContactSubmission, V2PaginationParams, V2List } from '../types';
7
+
8
+ export class ContactsV2Client extends BaseV2Client {
9
+
10
+ async submit(
11
+ siteName: string,
12
+ data: {
13
+ email: string;
14
+ message: string;
15
+ name?: string;
16
+ first_name?: string;
17
+ last_name?: string;
18
+ subject?: string;
19
+ phone?: string;
20
+ company?: string;
21
+ metadata?: Record<string, unknown>;
22
+ },
23
+ ): Promise<V2ContactSubmission> {
24
+ return this.post<V2ContactSubmission>(this.sitePath(siteName, 'contacts'), data);
25
+ }
26
+
27
+ async list(
28
+ siteName: string,
29
+ params?: V2PaginationParams & { status?: string },
30
+ ): Promise<V2List<V2ContactSubmission>> {
31
+ return this.getList<V2ContactSubmission>(this.sitePath(siteName, 'contacts'), params);
32
+ }
33
+
34
+ async get(siteName: string, id: string): Promise<V2ContactSubmission> {
35
+ return this.getOne<V2ContactSubmission>(this.sitePath(siteName, 'contacts', id));
36
+ }
37
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * v2 Content Client
3
+ */
4
+
5
+ import { BaseV2Client } from './base-v2-client';
6
+ import type {
7
+ V2Content, V2ContentCreateParams, V2ContentUpdateParams,
8
+ V2ContentListParams, V2List, V2Deleted,
9
+ } from '../types';
10
+
11
+ export class ContentV2Client extends BaseV2Client {
12
+
13
+ async list(siteName: string, params?: V2ContentListParams): Promise<V2List<V2Content>> {
14
+ return this.getList<V2Content>(this.sitePath(siteName, 'content'), params);
15
+ }
16
+
17
+ async *listAutoPaginated(siteName: string, params?: Omit<V2ContentListParams, 'starting_after' | 'ending_before'>) {
18
+ yield* this.listAutoPaginate<V2Content>(this.sitePath(siteName, 'content'), params);
19
+ }
20
+
21
+ async get(siteName: string, idOrSlug: string): Promise<V2Content> {
22
+ return this.getOne<V2Content>(this.sitePath(siteName, 'content', idOrSlug));
23
+ }
24
+
25
+ async create(siteName: string, data: V2ContentCreateParams): Promise<V2Content> {
26
+ return this.post<V2Content>(this.sitePath(siteName, 'content'), data);
27
+ }
28
+
29
+ async update(siteName: string, id: string, data: V2ContentUpdateParams): Promise<V2Content> {
30
+ return this.patchOne<V2Content>(this.sitePath(siteName, 'content', id), data);
31
+ }
32
+
33
+ async del(siteName: string, id: string): Promise<V2Deleted> {
34
+ return this.deleteOne(this.sitePath(siteName, 'content', id));
35
+ }
36
+
37
+ async publish(siteName: string, id: string): Promise<V2Content> {
38
+ return this.post<V2Content>(this.sitePath(siteName, 'content', `${id}/publish`));
39
+ }
40
+
41
+ async unpublish(siteName: string, id: string): Promise<V2Content> {
42
+ return this.post<V2Content>(this.sitePath(siteName, 'content', `${id}/unpublish`));
43
+ }
44
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * v2 Newsletter Client
3
+ */
4
+
5
+ import { BaseV2Client } from './base-v2-client';
6
+ import type {
7
+ V2NewsletterSubscription, V2NewsletterList, V2NewsletterCampaign,
8
+ V2PaginationParams, V2List,
9
+ } from '../types';
10
+
11
+ export class NewsletterV2Client extends BaseV2Client {
12
+
13
+ // --- Subscribe / Unsubscribe ---
14
+
15
+ async subscribe(
16
+ siteName: string,
17
+ data: {
18
+ email: string;
19
+ name?: string;
20
+ list_ids?: string[];
21
+ double_opt_in?: boolean;
22
+ source?: string;
23
+ source_url?: string;
24
+ frequency?: 'instant' | 'daily' | 'weekly' | 'monthly';
25
+ topics?: string[];
26
+ language?: string;
27
+ metadata?: Record<string, unknown>;
28
+ },
29
+ ): Promise<V2NewsletterSubscription> {
30
+ return this.post<V2NewsletterSubscription>(
31
+ this.sitePath(siteName, 'newsletter', 'subscribe'),
32
+ data,
33
+ );
34
+ }
35
+
36
+ async confirm(siteName: string, token: string): Promise<V2NewsletterSubscription> {
37
+ return this.getOne<V2NewsletterSubscription>(
38
+ this.sitePath(siteName, 'newsletter', `confirm/${token}`),
39
+ );
40
+ }
41
+
42
+ async unsubscribe(
43
+ siteName: string,
44
+ data: { token?: string; email?: string; reason?: string },
45
+ ): Promise<V2NewsletterSubscription> {
46
+ return this.post<V2NewsletterSubscription>(
47
+ this.sitePath(siteName, 'newsletter', 'unsubscribe'),
48
+ data,
49
+ );
50
+ }
51
+
52
+ // --- Subscriptions (admin) ---
53
+
54
+ async listSubscriptions(
55
+ siteName: string,
56
+ params?: V2PaginationParams & { status?: string },
57
+ ): Promise<V2List<V2NewsletterSubscription>> {
58
+ return this.getList<V2NewsletterSubscription>(
59
+ this.sitePath(siteName, 'newsletter', 'subscriptions'),
60
+ params,
61
+ );
62
+ }
63
+
64
+ async getSubscription(siteName: string, id: string): Promise<V2NewsletterSubscription> {
65
+ return this.getOne<V2NewsletterSubscription>(
66
+ this.sitePath(siteName, 'newsletter', `subscriptions/${id}`),
67
+ );
68
+ }
69
+
70
+ // --- Lists ---
71
+
72
+ async listLists(siteName: string): Promise<V2List<V2NewsletterList>> {
73
+ return this.getList<V2NewsletterList>(
74
+ this.sitePath(siteName, 'newsletter', 'lists'),
75
+ );
76
+ }
77
+
78
+ // --- Campaigns ---
79
+
80
+ async listCampaigns(
81
+ siteName: string,
82
+ params?: V2PaginationParams & { status?: string },
83
+ ): Promise<V2List<V2NewsletterCampaign>> {
84
+ return this.getList<V2NewsletterCampaign>(
85
+ this.sitePath(siteName, 'newsletter', 'campaigns'),
86
+ params,
87
+ );
88
+ }
89
+
90
+ async getCampaign(siteName: string, idOrSlug: string): Promise<V2NewsletterCampaign> {
91
+ return this.getOne<V2NewsletterCampaign>(
92
+ this.sitePath(siteName, 'newsletter', `campaigns/${idOrSlug}`),
93
+ );
94
+ }
95
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * v2 Orders Client (checkout sessions)
3
+ */
4
+
5
+ import { BaseV2Client } from './base-v2-client';
6
+ import type { V2Order, V2OrderListParams, V2List } from '../types';
7
+
8
+ export class OrdersV2Client extends BaseV2Client {
9
+
10
+ async list(siteName: string, params?: V2OrderListParams): Promise<V2List<V2Order>> {
11
+ return this.getList<V2Order>(this.sitePath(siteName, 'orders'), params);
12
+ }
13
+
14
+ async *listAutoPaginated(siteName: string, params?: Omit<V2OrderListParams, 'starting_after' | 'ending_before'>) {
15
+ yield* this.listAutoPaginate<V2Order>(this.sitePath(siteName, 'orders'), params);
16
+ }
17
+
18
+ async get(siteName: string, id: string): Promise<V2Order> {
19
+ return this.getOne<V2Order>(this.sitePath(siteName, 'orders', id));
20
+ }
21
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * v2 Organizations Client
3
+ */
4
+
5
+ import { BaseV2Client } from './base-v2-client';
6
+ import type { V2Organization, V2List } from '../types';
7
+
8
+ export class OrganizationsV2Client extends BaseV2Client {
9
+
10
+ async list(): Promise<V2List<V2Organization>> {
11
+ return this.getList<V2Organization>('/organizations');
12
+ }
13
+
14
+ async get(id: string): Promise<V2Organization> {
15
+ return this.getOne<V2Organization>(`/organizations/${id}`);
16
+ }
17
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * v2 Products Client
3
+ */
4
+
5
+ import { BaseV2Client } from './base-v2-client';
6
+ import type {
7
+ V2Product, V2ProductCreateParams, V2ProductUpdateParams,
8
+ V2ProductListParams, V2List, V2Deleted,
9
+ } from '../types';
10
+
11
+ export class ProductsV2Client extends BaseV2Client {
12
+
13
+ async list(siteName: string, params?: V2ProductListParams): Promise<V2List<V2Product>> {
14
+ return this.getList<V2Product>(this.sitePath(siteName, 'products'), params);
15
+ }
16
+
17
+ async *listAutoPaginated(siteName: string, params?: Omit<V2ProductListParams, 'starting_after' | 'ending_before'>) {
18
+ yield* this.listAutoPaginate<V2Product>(this.sitePath(siteName, 'products'), params);
19
+ }
20
+
21
+ async get(siteName: string, idOrSlug: string): Promise<V2Product> {
22
+ return this.getOne<V2Product>(this.sitePath(siteName, 'products', idOrSlug));
23
+ }
24
+
25
+ async create(siteName: string, data: V2ProductCreateParams): Promise<V2Product> {
26
+ return this.post<V2Product>(this.sitePath(siteName, 'products'), data);
27
+ }
28
+
29
+ async update(siteName: string, id: string, data: V2ProductUpdateParams): Promise<V2Product> {
30
+ return this.patchOne<V2Product>(this.sitePath(siteName, 'products', id), data);
31
+ }
32
+
33
+ async del(siteName: string, id: string): Promise<V2Deleted> {
34
+ return this.deleteOne(this.sitePath(siteName, 'products', id));
35
+ }
36
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * v2 Site Users Client
3
+ */
4
+
5
+ import { BaseV2Client } from './base-v2-client';
6
+ import type { V2SiteUser, V2SiteUserUpdateParams, V2SiteUserListParams, V2List } from '../types';
7
+
8
+ export interface V2OtpRequestResponse {
9
+ object: 'otp_request';
10
+ email: string;
11
+ expires_in: number;
12
+ }
13
+
14
+ export interface V2OtpVerifyResponse extends V2SiteUser {
15
+ token: string;
16
+ }
17
+
18
+ export class SiteUsersV2Client extends BaseV2Client {
19
+
20
+ // --- OTP Auth ---
21
+
22
+ async requestOtp(
23
+ siteName: string,
24
+ data: { email: string; waitlist?: boolean; metadata?: Record<string, unknown> },
25
+ ): Promise<V2OtpRequestResponse> {
26
+ return this.post<V2OtpRequestResponse>(this.sitePath(siteName, 'users', 'request-otp'), data);
27
+ }
28
+
29
+ async verifyOtp(
30
+ siteName: string,
31
+ data: { email: string; code: string },
32
+ ): Promise<V2OtpVerifyResponse> {
33
+ return this.post<V2OtpVerifyResponse>(this.sitePath(siteName, 'users', 'verify-otp'), data);
34
+ }
35
+
36
+ // --- Admin ---
37
+
38
+ async list(siteName: string, params?: V2SiteUserListParams): Promise<V2List<V2SiteUser>> {
39
+ return this.getList<V2SiteUser>(this.sitePath(siteName, 'users'), params);
40
+ }
41
+
42
+ async *listAutoPaginated(siteName: string, params?: Omit<V2SiteUserListParams, 'starting_after' | 'ending_before'>) {
43
+ yield* this.listAutoPaginate<V2SiteUser>(this.sitePath(siteName, 'users'), params);
44
+ }
45
+
46
+ async get(siteName: string, id: string): Promise<V2SiteUser> {
47
+ return this.getOne<V2SiteUser>(this.sitePath(siteName, 'users', id));
48
+ }
49
+
50
+ async update(siteName: string, id: string, data: V2SiteUserUpdateParams): Promise<V2SiteUser> {
51
+ return this.patchOne<V2SiteUser>(this.sitePath(siteName, 'users', id), data);
52
+ }
53
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * v2 Sites Client
3
+ */
4
+
5
+ import { BaseV2Client } from './base-v2-client';
6
+ import type { V2Site, V2PaginationParams, V2List } from '../types';
7
+
8
+ export class SitesV2Client extends BaseV2Client {
9
+
10
+ async list(params?: V2PaginationParams): Promise<V2List<V2Site>> {
11
+ return this.getList<V2Site>('/sites', params);
12
+ }
13
+
14
+ async get(name: string): Promise<V2Site> {
15
+ return this.getOne<V2Site>(`/sites/${encodeURIComponent(name)}`);
16
+ }
17
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * v2 Webhooks Client
3
+ */
4
+
5
+ import { BaseV2Client } from './base-v2-client';
6
+ import type {
7
+ V2Webhook, V2WebhookCreateParams, V2WebhookUpdateParams,
8
+ V2PaginationParams, V2List, V2Deleted,
9
+ } from '../types';
10
+
11
+ export class WebhooksV2Client extends BaseV2Client {
12
+
13
+ async list(siteName: string, params?: V2PaginationParams): Promise<V2List<V2Webhook>> {
14
+ return this.getList<V2Webhook>(this.sitePath(siteName, 'webhooks'), params);
15
+ }
16
+
17
+ async get(siteName: string, id: string): Promise<V2Webhook> {
18
+ return this.getOne<V2Webhook>(this.sitePath(siteName, 'webhooks', id));
19
+ }
20
+
21
+ async create(siteName: string, data: V2WebhookCreateParams): Promise<V2Webhook> {
22
+ return this.post<V2Webhook>(this.sitePath(siteName, 'webhooks'), data);
23
+ }
24
+
25
+ async update(siteName: string, id: string, data: V2WebhookUpdateParams): Promise<V2Webhook> {
26
+ return this.patchOne<V2Webhook>(this.sitePath(siteName, 'webhooks', id), data);
27
+ }
28
+
29
+ async del(siteName: string, id: string): Promise<V2Deleted> {
30
+ return this.deleteOne(this.sitePath(siteName, 'webhooks', id));
31
+ }
32
+ }