@bentonow/bento-node-sdk 0.2.1 → 1.0.5

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.
Files changed (76) hide show
  1. package/{LICENSE → LICENSE.md} +1 -1
  2. package/README.md +604 -1031
  3. package/dist/index.d.ts +9 -9
  4. package/dist/index.js +1306 -5
  5. package/dist/sdk/batch/enums.d.ts +8 -8
  6. package/dist/sdk/batch/errors.d.ts +18 -18
  7. package/dist/sdk/batch/events.d.ts +71 -71
  8. package/dist/sdk/batch/index.d.ts +55 -55
  9. package/dist/sdk/batch/types.d.ts +37 -37
  10. package/dist/sdk/broadcasts/index.d.ts +26 -0
  11. package/dist/sdk/broadcasts/types.d.ts +32 -0
  12. package/dist/sdk/client/errors.d.ts +12 -6
  13. package/dist/sdk/client/index.d.ts +94 -64
  14. package/dist/sdk/client/types.d.ts +3 -3
  15. package/dist/sdk/commands/enums.d.ts +12 -12
  16. package/dist/sdk/commands/index.d.ts +79 -79
  17. package/dist/sdk/commands/types.d.ts +32 -32
  18. package/dist/sdk/email-templates/index.d.ts +21 -0
  19. package/dist/sdk/email-templates/types.d.ts +23 -0
  20. package/dist/sdk/enums.d.ts +12 -9
  21. package/dist/sdk/errors.d.ts +2 -2
  22. package/dist/sdk/experimental/index.d.ts +75 -57
  23. package/dist/sdk/experimental/types.d.ts +71 -41
  24. package/dist/sdk/fields/index.d.ts +29 -29
  25. package/dist/sdk/fields/types.d.ts +17 -17
  26. package/dist/sdk/forms/index.d.ts +14 -14
  27. package/dist/sdk/forms/types.d.ts +28 -28
  28. package/dist/sdk/index.d.ts +11 -8
  29. package/dist/sdk/interfaces.d.ts +17 -13
  30. package/dist/sdk/sequences/index.d.ts +13 -0
  31. package/dist/sdk/sequences/types.d.ts +18 -0
  32. package/dist/sdk/stats/index.d.ts +24 -0
  33. package/dist/sdk/stats/types.d.ts +26 -0
  34. package/dist/sdk/subscribers/index.d.ts +20 -20
  35. package/dist/sdk/subscribers/types.d.ts +25 -25
  36. package/dist/sdk/tags/index.d.ts +20 -20
  37. package/dist/sdk/tags/types.d.ts +17 -17
  38. package/dist/sdk/types.d.ts +41 -41
  39. package/dist/sdk/workflows/index.d.ts +13 -0
  40. package/dist/sdk/workflows/types.d.ts +18 -0
  41. package/dist/versions/v1/index.d.ts +168 -132
  42. package/dist/versions/v1/types.d.ts +31 -31
  43. package/package.json +31 -44
  44. package/src/sdk/batch/events.ts +1 -1
  45. package/src/sdk/batch/index.ts +15 -22
  46. package/src/sdk/broadcasts/index.ts +44 -0
  47. package/src/sdk/broadcasts/types.ts +38 -0
  48. package/src/sdk/client/errors.ts +14 -0
  49. package/src/sdk/client/index.ts +205 -49
  50. package/src/sdk/commands/index.ts +54 -89
  51. package/src/sdk/email-templates/index.ts +39 -0
  52. package/src/sdk/email-templates/types.ts +27 -0
  53. package/src/sdk/enums.ts +3 -0
  54. package/src/sdk/experimental/index.ts +44 -26
  55. package/src/sdk/experimental/types.ts +35 -0
  56. package/src/sdk/fields/index.ts +1 -3
  57. package/src/sdk/forms/index.ts +4 -9
  58. package/src/sdk/forms/types.ts +1 -7
  59. package/src/sdk/index.ts +3 -0
  60. package/src/sdk/interfaces.ts +4 -0
  61. package/src/sdk/sequences/index.ts +21 -0
  62. package/src/sdk/sequences/types.ts +21 -0
  63. package/src/sdk/stats/index.ts +37 -0
  64. package/src/sdk/stats/types.ts +28 -0
  65. package/src/sdk/subscribers/index.ts +5 -15
  66. package/src/sdk/tags/index.ts +1 -3
  67. package/src/sdk/types.ts +1 -1
  68. package/src/sdk/workflows/index.ts +21 -0
  69. package/src/sdk/workflows/types.ts +21 -0
  70. package/src/versions/v1/index.ts +65 -10
  71. package/dist/bento-node-sdk.cjs.development.js +0 -2071
  72. package/dist/bento-node-sdk.cjs.development.js.map +0 -1
  73. package/dist/bento-node-sdk.cjs.production.min.js +0 -2
  74. package/dist/bento-node-sdk.cjs.production.min.js.map +0 -1
  75. package/dist/bento-node-sdk.esm.js +0 -2063
  76. package/dist/bento-node-sdk.esm.js.map +0 -1
@@ -11,3 +11,17 @@ export class RateLimitedError extends Error {
11
11
  this.name = 'RateLimitedError';
12
12
  }
13
13
  }
14
+
15
+ export class AuthorNotAuthorizedError extends Error {
16
+ constructor(message = 'Author not authorized to send on this account') {
17
+ super(message);
18
+ this.name = 'AuthorNotAuthorizedError';
19
+ }
20
+ }
21
+
22
+ export class RequestTimeoutError extends Error {
23
+ constructor(message = 'Request timed out') {
24
+ super(message);
25
+ this.name = 'RequestTimeoutError';
26
+ }
27
+ }
@@ -1,18 +1,57 @@
1
1
  import fetch from 'cross-fetch';
2
2
  import type { AnalyticsOptions, AuthenticationOptions } from '../interfaces';
3
- import { NotAuthorizedError, RateLimitedError } from './errors';
3
+ import {
4
+ NotAuthorizedError,
5
+ RateLimitedError,
6
+ AuthorNotAuthorizedError,
7
+ RequestTimeoutError,
8
+ } from './errors';
9
+
10
+ interface RequestOptions {
11
+ timeout?: number | null;
12
+ }
13
+
14
+ function encodeBase64(str: string): string {
15
+ if (typeof btoa === 'function') {
16
+ return btoa(str);
17
+ } else if (typeof Buffer !== 'undefined') {
18
+ return Buffer.from(str).toString('base64');
19
+ } else {
20
+ // Fallback implementation
21
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
22
+ let output = '';
23
+ for (
24
+ let block = 0, charCode, i = 0, map = chars;
25
+ str.charAt(i | 0) || ((map = '='), i % 1);
26
+ output += map.charAt(63 & (block >> (8 - (i % 1) * 8)))
27
+ ) {
28
+ charCode = str.charCodeAt((i += 3 / 4));
29
+
30
+ if (charCode > 0xff) {
31
+ throw new Error(
32
+ "'btoa' failed: The string to be encoded contains characters outside of the Latin1 range."
33
+ );
34
+ }
35
+
36
+ block = (block << 8) | charCode;
37
+ }
38
+ return output;
39
+ }
40
+ }
4
41
 
5
42
  export class BentoClient {
6
43
  private readonly _headers: HeadersInit = {};
7
44
  private readonly _baseUrl: string = 'https://app.bentonow.com/api/v1';
8
45
  private readonly _siteUuid: string = '';
9
46
  private readonly _logErrors: boolean = false;
47
+ private readonly _timeout: number = 30000; // 30 seconds default
10
48
 
11
49
  constructor(options: AnalyticsOptions) {
12
50
  this._baseUrl = options.clientOptions?.baseUrl || this._baseUrl;
13
51
  this._siteUuid = options.siteUuid;
14
- this._headers = this._extractHeaders(options.authentication);
52
+ this._headers = this._extractHeaders(options.authentication, options.siteUuid);
15
53
  this._logErrors = options.logErrors || false;
54
+ this._timeout = options.clientOptions?.timeout ?? this._timeout;
16
55
  }
17
56
 
18
57
  /**
@@ -23,27 +62,26 @@ export class BentoClient {
23
62
  * @param payload object
24
63
  * @returns Promise\<T\>
25
64
  * */
26
- public get<T>(
65
+ public async get<T>(
27
66
  endpoint: string,
28
- payload: Record<string, unknown> = {}
67
+ payload: Record<string, unknown> = {},
68
+ requestOptions: RequestOptions = {}
29
69
  ): Promise<T> {
30
- return new Promise<T>((resolve, reject) => {
31
- const queryParameters = this._getQueryParameters(payload);
70
+ const queryParameters = this._getQueryParameters(payload);
71
+ const url = `${this._baseUrl}${endpoint}?${queryParameters}`;
32
72
 
33
- fetch(`${this._baseUrl}${endpoint}?${queryParameters}`, {
73
+ const timeoutMs =
74
+ requestOptions.timeout === undefined ? this._timeout : requestOptions.timeout;
75
+ const response = await this._fetchWithTimeout(
76
+ url,
77
+ {
34
78
  method: 'GET',
35
79
  headers: this._headers,
36
- })
37
- .then(async result => {
38
- if (this._isSuccessfulStatus(result.status)) {
39
- return result.json();
40
- }
41
-
42
- throw await this._getErrorForResponse(result);
43
- })
44
- .then(data => resolve(data))
45
- .catch(error => reject(error));
46
- });
80
+ },
81
+ timeoutMs
82
+ );
83
+
84
+ return this._handleResponse<T>(response);
47
85
  }
48
86
 
49
87
  /**
@@ -54,47 +92,137 @@ export class BentoClient {
54
92
  * @param payload object
55
93
  * @returns Promise\<T\>
56
94
  * */
57
- public post<T>(
95
+ public async post<T>(
58
96
  endpoint: string,
59
- payload: Record<string, unknown> = {}
97
+ payload: Record<string, unknown> = {},
98
+ requestOptions: RequestOptions = {}
60
99
  ): Promise<T> {
61
- return new Promise<T>((resolve, reject) => {
62
- const body = this._getBody(payload);
100
+ const body = this._getBody(payload);
101
+ const url = `${this._baseUrl}${endpoint}`;
63
102
 
64
- fetch(`${this._baseUrl}${endpoint}`, {
103
+ const timeoutMs =
104
+ requestOptions.timeout === undefined ? this._timeout : requestOptions.timeout;
105
+ const response = await this._fetchWithTimeout(
106
+ url,
107
+ {
65
108
  method: 'POST',
66
109
  headers: {
67
110
  ...this._headers,
68
111
  'Content-Type': 'application/json',
69
112
  },
70
113
  body,
71
- })
72
- .then(async result => {
73
- if (this._isSuccessfulStatus(result.status)) {
74
- return result.json();
75
- }
76
-
77
- throw await this._getErrorForResponse(result);
78
- })
79
- .then(data => resolve(data))
80
- .catch(error => reject(error));
81
- });
114
+ },
115
+ timeoutMs
116
+ );
117
+
118
+ return this._handleResponse<T>(response);
119
+ }
120
+
121
+ /**
122
+ * Wraps a PATCH request to the Bento API and automatically adds the required
123
+ * headers.
124
+ *
125
+ * @param endpoint string
126
+ * @param payload object
127
+ * @returns Promise\<T\>
128
+ * */
129
+ public async patch<T>(
130
+ endpoint: string,
131
+ payload: Record<string, unknown> = {},
132
+ requestOptions: RequestOptions = {}
133
+ ): Promise<T> {
134
+ const body = this._getBody(payload);
135
+ const url = `${this._baseUrl}${endpoint}`;
136
+
137
+ const timeoutMs =
138
+ requestOptions.timeout === undefined ? this._timeout : requestOptions.timeout;
139
+ const response = await this._fetchWithTimeout(
140
+ url,
141
+ {
142
+ method: 'PATCH',
143
+ headers: {
144
+ ...this._headers,
145
+ 'Content-Type': 'application/json',
146
+ },
147
+ body,
148
+ },
149
+ timeoutMs
150
+ );
151
+
152
+ return this._handleResponse<T>(response);
153
+ }
154
+
155
+ /**
156
+ * Performs a fetch request with a configurable timeout.
157
+ *
158
+ * @param url The URL to fetch
159
+ * @param options Fetch options
160
+ * @returns Promise<Response>
161
+ */
162
+ private async _fetchWithTimeout(
163
+ url: string,
164
+ options: RequestInit,
165
+ timeout: number | null
166
+ ): Promise<Response> {
167
+ if (timeout === null) {
168
+ return fetch(url, options);
169
+ }
170
+
171
+ const controller = new AbortController();
172
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
173
+
174
+ try {
175
+ const response = await fetch(url, {
176
+ ...options,
177
+ signal: controller.signal,
178
+ });
179
+ return response;
180
+ } catch (error: unknown) {
181
+ if (error instanceof Error && error.name === 'AbortError') {
182
+ throw new RequestTimeoutError(`Request timed out after ${timeout}ms`);
183
+ }
184
+ throw error;
185
+ } finally {
186
+ clearTimeout(timeoutId);
187
+ }
82
188
  }
83
189
 
84
190
  /**
85
- * Extracts the `publishableKey` and `secretKey` from the `authentication` options and
86
- * adds the `Authorization` header.
191
+ * Handles the response from a fetch request, parsing JSON or throwing appropriate errors.
192
+ *
193
+ * @param response The fetch Response object
194
+ * @returns Promise<T> The parsed response data
195
+ */
196
+ private async _handleResponse<T>(response: Response): Promise<T> {
197
+ if (this._isSuccessfulStatus(response.status)) {
198
+ try {
199
+ const data = await response.json();
200
+ return data as T;
201
+ } catch {
202
+ // If JSON parsing fails on a successful response, throw a descriptive error
203
+ throw new Error(`[${response.status}] - Invalid JSON response from server`);
204
+ }
205
+ }
206
+
207
+ throw await this._getErrorForResponse(response);
208
+ }
209
+
210
+ /**
211
+ * Extracts the `publishableKey` and `secretKey` from the `authentication` options,
212
+ * adds the `Authorization` header, and includes a `User-Agent` header with the site UUID.
87
213
  *
88
214
  * @param authentication AuthenticationOptions
215
+ * @param siteUuid string The site UUID to be included in the User-Agent header
89
216
  * @returns HeadersInit
90
217
  */
91
- private _extractHeaders(authentication: AuthenticationOptions): HeadersInit {
92
- const authenticationKey = Buffer.from(
218
+ private _extractHeaders(authentication: AuthenticationOptions, siteUuid: string): HeadersInit {
219
+ const authenticationKey = encodeBase64(
93
220
  `${authentication.publishableKey}:${authentication.secretKey}`
94
- ).toString('base64');
221
+ );
95
222
 
96
223
  return {
97
224
  Authorization: `Basic ${authenticationKey}`,
225
+ 'User-Agent': `bento-node-${siteUuid}`,
98
226
  };
99
227
  }
100
228
 
@@ -127,7 +255,7 @@ export class BentoClient {
127
255
 
128
256
  const queryParameters = new URLSearchParams();
129
257
  for (const [key, value] of Object.entries(body)) {
130
- queryParameters.append(key, value);
258
+ queryParameters.append(key, String(value));
131
259
  }
132
260
 
133
261
  return queryParameters.toString();
@@ -161,17 +289,45 @@ export class BentoClient {
161
289
 
162
290
  const contentType = response.headers.get('Content-Type');
163
291
  let responseMessage = '';
292
+ let json: Record<string, unknown> | null = null;
164
293
 
165
- switch (contentType?.toLocaleLowerCase()) {
166
- case 'text/plain':
167
- responseMessage = await response.text();
168
- break;
169
- case 'application/json':
170
- responseMessage = JSON.stringify(await response.json());
171
- break;
172
- default:
294
+ // Try to parse the response body based on content type
295
+ try {
296
+ if (contentType?.toLowerCase().includes('application/json')) {
297
+ // For JSON content type, try to parse as JSON
298
+ try {
299
+ json = await response.json();
300
+ } catch {
301
+ responseMessage = 'Unable to parse JSON response';
302
+ }
303
+ } else if (contentType?.toLowerCase().includes('text/plain')) {
304
+ // For text/plain content type, read as text
305
+ try {
306
+ responseMessage = await response.text();
307
+ } catch {
308
+ responseMessage = 'Unable to read text response';
309
+ }
310
+ } else {
311
+ // For unknown content types, use default message
173
312
  responseMessage = 'Unknown response from the Bento API.';
174
- break;
313
+ }
314
+ } catch {
315
+ responseMessage = 'Unable to read response body';
316
+ }
317
+
318
+ // Check for author not authorized error in JSON response
319
+ if (json && json.error === 'Author not authorized to send on this account') {
320
+ return new AuthorNotAuthorizedError(json.error as string);
321
+ }
322
+
323
+ // If we have JSON but no specific error match, use the JSON string
324
+ if (json) {
325
+ responseMessage = JSON.stringify(json);
326
+ }
327
+
328
+ // If we still don't have a message, use a default
329
+ if (!responseMessage) {
330
+ responseMessage = 'Unknown response from the Bento API.';
175
331
  }
176
332
 
177
333
  return new Error(`[${response.status}] - ${responseMessage}`);
@@ -30,19 +30,14 @@ export class BentoCommands<S> {
30
30
  * @param parameters \{ email: string, tagName: string \}
31
31
  * @returns Promise\<Subscriber | null\>
32
32
  */
33
- public async addTag(
34
- parameters: AddTagParameters
35
- ): Promise<Subscriber<S> | null> {
36
- const result = await this._client.post<DataResponse<Subscriber<S>>>(
37
- this._url,
38
- {
39
- command: {
40
- command: CommandTypes.ADD_TAG,
41
- email: parameters.email,
42
- query: parameters.tagName,
43
- },
44
- }
45
- );
33
+ public async addTag(parameters: AddTagParameters): Promise<Subscriber<S> | null> {
34
+ const result = await this._client.post<DataResponse<Subscriber<S>>>(this._url, {
35
+ command: {
36
+ command: CommandTypes.ADD_TAG,
37
+ email: parameters.email,
38
+ query: parameters.tagName,
39
+ },
40
+ });
46
41
 
47
42
  if (Object.keys(result).length === 0 || !result.data) return null;
48
43
  return result.data;
@@ -54,19 +49,14 @@ export class BentoCommands<S> {
54
49
  * @param parameters \{ email: string, tagName: string \}
55
50
  * @returns Promise\<Subscriber | null\>
56
51
  */
57
- public async removeTag(
58
- parameters: RemoveTagParameters
59
- ): Promise<Subscriber<S> | null> {
60
- const result = await this._client.post<DataResponse<Subscriber<S>>>(
61
- this._url,
62
- {
63
- command: {
64
- command: CommandTypes.REMOVE_TAG,
65
- email: parameters.email,
66
- query: parameters.tagName,
67
- },
68
- }
69
- );
52
+ public async removeTag(parameters: RemoveTagParameters): Promise<Subscriber<S> | null> {
53
+ const result = await this._client.post<DataResponse<Subscriber<S>>>(this._url, {
54
+ command: {
55
+ command: CommandTypes.REMOVE_TAG,
56
+ email: parameters.email,
57
+ query: parameters.tagName,
58
+ },
59
+ });
70
60
 
71
61
  if (Object.keys(result).length === 0 || !result.data) return null;
72
62
  return result.data;
@@ -84,19 +74,14 @@ export class BentoCommands<S> {
84
74
  * @param parameters \{ email: string, field: \{ key: string; value: string; \} \}
85
75
  * @returns Promise\<Subscriber | null\>
86
76
  */
87
- public async addField(
88
- parameters: AddFieldParameters<S>
89
- ): Promise<Subscriber<S> | null> {
90
- const result = await this._client.post<DataResponse<Subscriber<S>>>(
91
- this._url,
92
- {
93
- command: {
94
- command: CommandTypes.ADD_FIELD,
95
- email: parameters.email,
96
- query: parameters.field,
97
- },
98
- }
99
- );
77
+ public async addField(parameters: AddFieldParameters<S>): Promise<Subscriber<S> | null> {
78
+ const result = await this._client.post<DataResponse<Subscriber<S>>>(this._url, {
79
+ command: {
80
+ command: CommandTypes.ADD_FIELD,
81
+ email: parameters.email,
82
+ query: parameters.field,
83
+ },
84
+ });
100
85
 
101
86
  if (Object.keys(result).length === 0 || !result.data) return null;
102
87
  return result.data;
@@ -108,19 +93,14 @@ export class BentoCommands<S> {
108
93
  * @param parameters \{ email: string, fieldName: string \}
109
94
  * @returns Promise\<Subscriber | null\>
110
95
  */
111
- public async removeField(
112
- parameters: RemoveFieldParameters<S>
113
- ): Promise<Subscriber<S> | null> {
114
- const result = await this._client.post<DataResponse<Subscriber<S>>>(
115
- this._url,
116
- {
117
- command: {
118
- command: CommandTypes.REMOVE_FIELD,
119
- email: parameters.email,
120
- query: parameters.fieldName,
121
- },
122
- }
123
- );
96
+ public async removeField(parameters: RemoveFieldParameters<S>): Promise<Subscriber<S> | null> {
97
+ const result = await this._client.post<DataResponse<Subscriber<S>>>(this._url, {
98
+ command: {
99
+ command: CommandTypes.REMOVE_FIELD,
100
+ email: parameters.email,
101
+ query: parameters.fieldName,
102
+ },
103
+ });
124
104
 
125
105
  if (Object.keys(result).length === 0 || !result.data) return null;
126
106
  return result.data;
@@ -136,18 +116,13 @@ export class BentoCommands<S> {
136
116
  * @param parameters \{ email: string \}
137
117
  * @returns Promise\<Subscriber | null\>
138
118
  */
139
- public async subscribe(
140
- parameters: SubscribeParameters
141
- ): Promise<Subscriber<S> | null> {
142
- const result = await this._client.post<DataResponse<Subscriber<S>>>(
143
- this._url,
144
- {
145
- command: {
146
- command: CommandTypes.SUBSCRIBE,
147
- email: parameters.email,
148
- },
149
- }
150
- );
119
+ public async subscribe(parameters: SubscribeParameters): Promise<Subscriber<S> | null> {
120
+ const result = await this._client.post<DataResponse<Subscriber<S>>>(this._url, {
121
+ command: {
122
+ command: CommandTypes.SUBSCRIBE,
123
+ email: parameters.email,
124
+ },
125
+ });
151
126
 
152
127
  if (Object.keys(result).length === 0 || !result.data) return null;
153
128
  return result.data;
@@ -164,18 +139,13 @@ export class BentoCommands<S> {
164
139
  * @param parameters \{ email: string \}
165
140
  * @returns Promise\<Subscriber | null\>
166
141
  */
167
- public async unsubscribe(
168
- parameters: UnsubscribeParameters
169
- ): Promise<Subscriber<S> | null> {
170
- const result = await this._client.post<DataResponse<Subscriber<S>>>(
171
- this._url,
172
- {
173
- command: {
174
- command: CommandTypes.UNSUBSCRIBE,
175
- email: parameters.email,
176
- },
177
- }
178
- );
142
+ public async unsubscribe(parameters: UnsubscribeParameters): Promise<Subscriber<S> | null> {
143
+ const result = await this._client.post<DataResponse<Subscriber<S>>>(this._url, {
144
+ command: {
145
+ command: CommandTypes.UNSUBSCRIBE,
146
+ email: parameters.email,
147
+ },
148
+ });
179
149
 
180
150
  if (Object.keys(result).length === 0 || !result.data) return null;
181
151
  return result.data;
@@ -187,19 +157,14 @@ export class BentoCommands<S> {
187
157
  * @param parameters \{ oldEmail: string, newEmail: string \}
188
158
  * @returns Promise\<Subscriber | null\>
189
159
  */
190
- public async changeEmail(
191
- parameters: ChangeEmailParameters
192
- ): Promise<Subscriber<S> | null> {
193
- const result = await this._client.post<DataResponse<Subscriber<S>>>(
194
- this._url,
195
- {
196
- command: {
197
- command: CommandTypes.CHANGE_EMAIL,
198
- email: parameters.oldEmail,
199
- query: parameters.newEmail,
200
- },
201
- }
202
- );
160
+ public async changeEmail(parameters: ChangeEmailParameters): Promise<Subscriber<S> | null> {
161
+ const result = await this._client.post<DataResponse<Subscriber<S>>>(this._url, {
162
+ command: {
163
+ command: CommandTypes.CHANGE_EMAIL,
164
+ email: parameters.oldEmail,
165
+ query: parameters.newEmail,
166
+ },
167
+ });
203
168
 
204
169
  if (Object.keys(result).length === 0 || !result.data) return null;
205
170
  return result.data;
@@ -0,0 +1,39 @@
1
+ import type { BentoClient } from '../client';
2
+ import type { DataResponse } from '../client/types';
3
+ import type { EmailTemplate, GetEmailTemplateParameters, UpdateEmailTemplateParameters } from './types';
4
+
5
+ export class BentoEmailTemplates {
6
+ private readonly _url = '/fetch/emails/templates';
7
+
8
+ constructor(private readonly _client: BentoClient) {}
9
+
10
+ /**
11
+ * Returns a single email template by ID.
12
+ *
13
+ * @param parameters GetEmailTemplateParameters
14
+ * @returns Promise\<EmailTemplate | null\>
15
+ */
16
+ public async getEmailTemplate(parameters: GetEmailTemplateParameters): Promise<EmailTemplate | null> {
17
+ const result = await this._client.get<DataResponse<EmailTemplate>>(`${this._url}/${parameters.id}`);
18
+
19
+ if (Object.keys(result).length === 0 || !result.data) return null;
20
+ return result.data;
21
+ }
22
+
23
+ /**
24
+ * Updates an email template's subject and/or HTML content.
25
+ *
26
+ * @param parameters UpdateEmailTemplateParameters
27
+ * @returns Promise\<EmailTemplate | null\>
28
+ */
29
+ public async updateEmailTemplate(parameters: UpdateEmailTemplateParameters): Promise<EmailTemplate | null> {
30
+ const { id, ...updateFields } = parameters;
31
+
32
+ const result = await this._client.patch<DataResponse<EmailTemplate>>(`${this._url}/${id}`, {
33
+ email_template: updateFields,
34
+ });
35
+
36
+ if (Object.keys(result).length === 0 || !result.data) return null;
37
+ return result.data;
38
+ }
39
+ }
@@ -0,0 +1,27 @@
1
+ import type { BaseEntity } from '../types';
2
+
3
+ /**
4
+ * Email Template Method Parameter Types
5
+ */
6
+ export type GetEmailTemplateParameters = {
7
+ id: number;
8
+ };
9
+
10
+ export type UpdateEmailTemplateParameters = {
11
+ id: number;
12
+ subject?: string;
13
+ html?: string;
14
+ };
15
+
16
+ /**
17
+ * Core Email Template Types
18
+ */
19
+ export type EmailTemplateAttributes = {
20
+ name: string;
21
+ subject: string;
22
+ html: string;
23
+ created_at: string;
24
+ stats: Record<string, unknown> | null;
25
+ };
26
+
27
+ export type EmailTemplate = BaseEntity<EmailTemplateAttributes>;
package/src/sdk/enums.ts CHANGED
@@ -6,4 +6,7 @@ export enum EntityType {
6
6
  TAGS = 'tags',
7
7
  VISITORS = 'visitors',
8
8
  VISITORS_FIELDS = 'visitors-fields',
9
+ SEQUENCES = 'sequences',
10
+ WORKFLOWS = 'workflows',
11
+ EMAIL_TEMPLATES = 'email_templates',
9
12
  }