@postbrix/sdk 1.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.
- package/LICENSE +279 -0
- package/README.md +325 -0
- package/dist/index.cjs +2 -0
- package/dist/index.d.mts +361 -0
- package/dist/index.d.ts +361 -0
- package/dist/index.mjs +2 -0
- package/package.json +74 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration options for PostBrix client
|
|
3
|
+
*/
|
|
4
|
+
interface PostBrixConfig {
|
|
5
|
+
/** API key for authentication (required) */
|
|
6
|
+
apiKey: string;
|
|
7
|
+
/** Base URL for API endpoint (default: https://api.postbrix.com/v1) */
|
|
8
|
+
baseURL?: string;
|
|
9
|
+
/** Request timeout in milliseconds (default: 30000) */
|
|
10
|
+
timeout?: number;
|
|
11
|
+
/** Maximum number of retry attempts for transient errors (default: 2) */
|
|
12
|
+
maxRetries?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Output format for rendered templates
|
|
16
|
+
*/
|
|
17
|
+
type RenderFormat = "html" | "mjml" | "both";
|
|
18
|
+
/**
|
|
19
|
+
* Options for render request
|
|
20
|
+
*
|
|
21
|
+
* @typeParam TFormat - Desired output format (default: RenderFormat)
|
|
22
|
+
*/
|
|
23
|
+
interface RenderOptions<TFormat extends RenderFormat = RenderFormat> {
|
|
24
|
+
/** Output format (default: "html") */
|
|
25
|
+
format?: TFormat;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Render request body
|
|
29
|
+
*/
|
|
30
|
+
interface RenderRequest {
|
|
31
|
+
variables?: Record<string, unknown>;
|
|
32
|
+
format: RenderFormat;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* HTML render response
|
|
36
|
+
*/
|
|
37
|
+
interface HtmlRenderResponse {
|
|
38
|
+
format: "html";
|
|
39
|
+
html: string;
|
|
40
|
+
mjml?: never;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* MJML render response
|
|
44
|
+
*/
|
|
45
|
+
interface MjmlRenderResponse {
|
|
46
|
+
format: "mjml";
|
|
47
|
+
mjml: string;
|
|
48
|
+
html?: never;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Both formats render response
|
|
52
|
+
*/
|
|
53
|
+
interface BothFormatsRenderResponse {
|
|
54
|
+
format: "both";
|
|
55
|
+
html: string;
|
|
56
|
+
mjml: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Render response discriminated union
|
|
60
|
+
*/
|
|
61
|
+
type RenderResponse = HtmlRenderResponse | MjmlRenderResponse | BothFormatsRenderResponse;
|
|
62
|
+
/**
|
|
63
|
+
* Resolves concrete return type based on requested format:
|
|
64
|
+
* - "html" -> HtmlRenderResponse (guaranteed .html: string)
|
|
65
|
+
* - "mjml" -> MjmlRenderResponse (guaranteed .mjml: string)
|
|
66
|
+
* - "both" -> BothFormatsRenderResponse (guaranteed .html: string, .mjml: string)
|
|
67
|
+
* - RenderFormat / union -> RenderResponse (discriminated union)
|
|
68
|
+
*/
|
|
69
|
+
type RenderResult<TFormat extends RenderFormat = "html"> = [
|
|
70
|
+
TFormat
|
|
71
|
+
] extends ["mjml"] ? MjmlRenderResponse : [TFormat] extends ["both"] ? BothFormatsRenderResponse : [TFormat] extends ["html"] ? HtmlRenderResponse : RenderResponse;
|
|
72
|
+
/**
|
|
73
|
+
* API error response from server
|
|
74
|
+
*/
|
|
75
|
+
interface ApiErrorResponse {
|
|
76
|
+
message?: string;
|
|
77
|
+
error?: string;
|
|
78
|
+
code?: string;
|
|
79
|
+
statusCode?: number;
|
|
80
|
+
requestId?: string;
|
|
81
|
+
details?: unknown;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* HTTP response wrapper
|
|
85
|
+
*/
|
|
86
|
+
interface HttpResponse<T> {
|
|
87
|
+
data: T;
|
|
88
|
+
status: number;
|
|
89
|
+
headers: Record<string, string>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* PostBrix SDK Client
|
|
94
|
+
*
|
|
95
|
+
* Main entry point for interacting with the PostBrix Template Render API.
|
|
96
|
+
* Provides zero-dependency, strongly typed template rendering with built-in
|
|
97
|
+
* exponential jitter retries, timeout management, and comprehensive error handling.
|
|
98
|
+
*/
|
|
99
|
+
declare class PostBrix {
|
|
100
|
+
private readonly apiKey;
|
|
101
|
+
private readonly baseURL;
|
|
102
|
+
private readonly timeout;
|
|
103
|
+
private readonly maxRetries;
|
|
104
|
+
private static readonly DEFAULT_BASE_URL;
|
|
105
|
+
private static readonly DEFAULT_TIMEOUT;
|
|
106
|
+
private static readonly DEFAULT_MAX_RETRIES;
|
|
107
|
+
/**
|
|
108
|
+
* Initialize a new PostBrix SDK client instance.
|
|
109
|
+
*
|
|
110
|
+
* @param config - Configuration options for the client
|
|
111
|
+
* @param config.apiKey - PostBrix API key for authentication (starts with `pb_live_` or `pb_test_`)
|
|
112
|
+
* @param config.baseURL - Base URL for the PostBrix API (default: `https://api.postbrix.com/v1`)
|
|
113
|
+
* @param config.timeout - Request timeout in milliseconds (default: `30000`)
|
|
114
|
+
* @param config.maxRetries - Maximum retry attempts for transient errors (default: `2`)
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* import { PostBrix } from "@postbrix/sdk";
|
|
119
|
+
*
|
|
120
|
+
* // Standard initialization
|
|
121
|
+
* const postbrix = new PostBrix({
|
|
122
|
+
* apiKey: process.env.POSTBRIX_API_KEY!,
|
|
123
|
+
* });
|
|
124
|
+
*
|
|
125
|
+
* // Custom timeout and retries
|
|
126
|
+
* const postbrix = new PostBrix({
|
|
127
|
+
* apiKey: "pb_live_xxxxxxxxxxxxxxxxxxxxxxxx",
|
|
128
|
+
* baseURL: "https://api.postbrix.com/v1",
|
|
129
|
+
* timeout: 10000,
|
|
130
|
+
* maxRetries: 3,
|
|
131
|
+
* });
|
|
132
|
+
* ```
|
|
133
|
+
*
|
|
134
|
+
* @throws {PostBrixValidationError} If any configuration parameter is invalid
|
|
135
|
+
*/
|
|
136
|
+
constructor(config: PostBrixConfig);
|
|
137
|
+
/**
|
|
138
|
+
* Validate configuration options in detail
|
|
139
|
+
*
|
|
140
|
+
* @param config - Configuration object to validate
|
|
141
|
+
* @throws {PostBrixValidationError} If configuration is missing or invalid
|
|
142
|
+
*/
|
|
143
|
+
private validateConfig;
|
|
144
|
+
/**
|
|
145
|
+
* Returns active client configuration with sensitive secrets securely masked.
|
|
146
|
+
* Useful for debugging, logging, and environment verification.
|
|
147
|
+
*
|
|
148
|
+
* @returns Read-only sanitized configuration object
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```ts
|
|
152
|
+
* const config = postbrix.getConfig();
|
|
153
|
+
* console.log(config);
|
|
154
|
+
* // { baseURL: 'https://api.postbrix.com/v1', timeout: 30000, maxRetries: 2, apiKey: 'pb_live...[REDACTED]' }
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
getConfig(): Readonly<{
|
|
158
|
+
baseURL: string;
|
|
159
|
+
timeout: number;
|
|
160
|
+
maxRetries: number;
|
|
161
|
+
apiKey: string;
|
|
162
|
+
}>;
|
|
163
|
+
/**
|
|
164
|
+
* Render an email template with dynamic variables.
|
|
165
|
+
*
|
|
166
|
+
* @typeParam TVariables - Shape of dynamic variables passed to the template
|
|
167
|
+
* @typeParam TFormat - Desired output format: "html" (default), "mjml", or "both"
|
|
168
|
+
*
|
|
169
|
+
* @param templateId - Template identifier or slug (e.g. "welcome-email")
|
|
170
|
+
* @param variables - Key-value variables to substitute into template tags (must be JSON-serializable)
|
|
171
|
+
* @param options - Configuration options for rendering (e.g. `{ format: "html" }`)
|
|
172
|
+
* @returns Promise resolving to the rendered template output matching the requested format
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```ts
|
|
176
|
+
* // 1. Render HTML (default - returns { format: "html", html: string })
|
|
177
|
+
* const result = await postbrix.render("welcome-email", { name: "Alex" });
|
|
178
|
+
* console.log(result.html);
|
|
179
|
+
*
|
|
180
|
+
* // 2. Render MJML (returns { format: "mjml", mjml: string })
|
|
181
|
+
* const mjmlResult = await postbrix.render("welcome-email", { name: "Alex" }, { format: "mjml" });
|
|
182
|
+
* console.log(mjmlResult.mjml);
|
|
183
|
+
*
|
|
184
|
+
* // 3. Render Both (returns { format: "both", html: string, mjml: string })
|
|
185
|
+
* const bothResult = await postbrix.render("welcome-email", { name: "Alex" }, { format: "both" });
|
|
186
|
+
* console.log(bothResult.html, bothResult.mjml);
|
|
187
|
+
* ```
|
|
188
|
+
*
|
|
189
|
+
* @throws {PostBrixValidationError} If templateId, variables, format, or circular data are invalid
|
|
190
|
+
* @throws {PostBrixAuthenticationError} If the API key is missing or invalid (401)
|
|
191
|
+
* @throws {PostBrixForbiddenError} If API key lacks permissions (403)
|
|
192
|
+
* @throws {PostBrixNotFoundError} If templateId does not exist (404)
|
|
193
|
+
* @throws {PostBrixRateLimitError} If rate limit is exceeded (429)
|
|
194
|
+
* @throws {PostBrixServerError} If server encounters an internal error (500, 502, 503, 504)
|
|
195
|
+
* @throws {PostBrixTimeoutError} If request times out
|
|
196
|
+
* @throws {PostBrixNetworkError} If network connection fails
|
|
197
|
+
*/
|
|
198
|
+
render<TVariables extends Record<string, unknown> = Record<string, unknown>, TFormat extends RenderFormat = "html">(templateId: string, variables?: TVariables, options?: RenderOptions<TFormat>): Promise<RenderResult<TFormat>>;
|
|
199
|
+
/**
|
|
200
|
+
* Internal request method with retry logic
|
|
201
|
+
*/
|
|
202
|
+
private request;
|
|
203
|
+
/**
|
|
204
|
+
* Execute a single request
|
|
205
|
+
*/
|
|
206
|
+
private executeRequest;
|
|
207
|
+
/**
|
|
208
|
+
* Build request headers
|
|
209
|
+
*/
|
|
210
|
+
private buildHeaders;
|
|
211
|
+
/**
|
|
212
|
+
* Handle successful response with schema validation and backend unwrap
|
|
213
|
+
*/
|
|
214
|
+
private handleSuccessResponse;
|
|
215
|
+
/**
|
|
216
|
+
* Handle error response
|
|
217
|
+
*/
|
|
218
|
+
private handleErrorResponse;
|
|
219
|
+
/**
|
|
220
|
+
* Determine if error is retryable
|
|
221
|
+
*/
|
|
222
|
+
private isRetryableError;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Base error class for all PostBrix SDK errors
|
|
227
|
+
*/
|
|
228
|
+
declare class PostBrixError extends Error {
|
|
229
|
+
readonly statusCode?: number;
|
|
230
|
+
readonly requestId?: string;
|
|
231
|
+
readonly code: string;
|
|
232
|
+
readonly retryable: boolean;
|
|
233
|
+
readonly details?: unknown;
|
|
234
|
+
readonly cause?: Error;
|
|
235
|
+
constructor(message: string, options?: {
|
|
236
|
+
statusCode?: number;
|
|
237
|
+
requestId?: string;
|
|
238
|
+
code?: string;
|
|
239
|
+
retryable?: boolean;
|
|
240
|
+
details?: unknown;
|
|
241
|
+
cause?: Error;
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Error thrown when request times out
|
|
246
|
+
*/
|
|
247
|
+
declare class PostBrixTimeoutError extends PostBrixError {
|
|
248
|
+
constructor(timeoutMs: number, options?: {
|
|
249
|
+
requestId?: string;
|
|
250
|
+
cause?: Error;
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Error thrown for network issues
|
|
255
|
+
*/
|
|
256
|
+
declare class PostBrixNetworkError extends PostBrixError {
|
|
257
|
+
constructor(message: string, options?: {
|
|
258
|
+
requestId?: string;
|
|
259
|
+
retryable?: boolean;
|
|
260
|
+
cause?: Error;
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Error thrown for validation issues
|
|
265
|
+
*/
|
|
266
|
+
declare class PostBrixValidationError extends PostBrixError {
|
|
267
|
+
constructor(message: string, options?: {
|
|
268
|
+
statusCode?: number;
|
|
269
|
+
requestId?: string;
|
|
270
|
+
code?: string;
|
|
271
|
+
details?: unknown;
|
|
272
|
+
cause?: Error;
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Error thrown for authentication/authorization issues
|
|
277
|
+
*/
|
|
278
|
+
declare class PostBrixAuthError extends PostBrixError {
|
|
279
|
+
constructor(message: string, options?: {
|
|
280
|
+
statusCode?: number;
|
|
281
|
+
requestId?: string;
|
|
282
|
+
code?: string;
|
|
283
|
+
details?: unknown;
|
|
284
|
+
cause?: Error;
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Error thrown when rate limit is exceeded
|
|
289
|
+
*/
|
|
290
|
+
declare class PostBrixLimitError extends PostBrixError {
|
|
291
|
+
readonly retryAfter?: number;
|
|
292
|
+
constructor(message: string, options?: {
|
|
293
|
+
statusCode?: number;
|
|
294
|
+
requestId?: string;
|
|
295
|
+
code?: string;
|
|
296
|
+
retryAfter?: number;
|
|
297
|
+
details?: unknown;
|
|
298
|
+
cause?: Error;
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Error thrown when template is not found
|
|
303
|
+
*/
|
|
304
|
+
declare class PostBrixTemplateError extends PostBrixError {
|
|
305
|
+
constructor(message: string, options?: {
|
|
306
|
+
statusCode?: number;
|
|
307
|
+
requestId?: string;
|
|
308
|
+
code?: string;
|
|
309
|
+
details?: unknown;
|
|
310
|
+
cause?: Error;
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Error thrown when API returns a malformed response
|
|
315
|
+
*/
|
|
316
|
+
declare class PostBrixResponseError extends PostBrixError {
|
|
317
|
+
constructor(message: string, options?: {
|
|
318
|
+
statusCode?: number;
|
|
319
|
+
requestId?: string;
|
|
320
|
+
code?: string;
|
|
321
|
+
details?: unknown;
|
|
322
|
+
cause?: Error;
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Error thrown when access is forbidden (403)
|
|
327
|
+
*/
|
|
328
|
+
declare class PostBrixForbiddenError extends PostBrixAuthError {
|
|
329
|
+
constructor(message: string, options?: {
|
|
330
|
+
statusCode?: number;
|
|
331
|
+
requestId?: string;
|
|
332
|
+
code?: string;
|
|
333
|
+
details?: unknown;
|
|
334
|
+
cause?: Error;
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Error thrown for server-side failures (5xx)
|
|
339
|
+
*/
|
|
340
|
+
declare class PostBrixServerError extends PostBrixError {
|
|
341
|
+
constructor(message: string, options?: {
|
|
342
|
+
statusCode?: number;
|
|
343
|
+
requestId?: string;
|
|
344
|
+
code?: string;
|
|
345
|
+
retryable?: boolean;
|
|
346
|
+
details?: unknown;
|
|
347
|
+
cause?: Error;
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
/** Canonical Aliases for Error Classes */
|
|
351
|
+
declare const PostBrixAuthenticationError: typeof PostBrixAuthError;
|
|
352
|
+
type PostBrixAuthenticationError = PostBrixAuthError;
|
|
353
|
+
declare const PostBrixRateLimitError: typeof PostBrixLimitError;
|
|
354
|
+
type PostBrixRateLimitError = PostBrixLimitError;
|
|
355
|
+
declare const PostBrixNotFoundError: typeof PostBrixTemplateError;
|
|
356
|
+
type PostBrixNotFoundError = PostBrixTemplateError;
|
|
357
|
+
|
|
358
|
+
declare const SDK_VERSION = "1.0.0";
|
|
359
|
+
declare const VERSION = "1.0.0";
|
|
360
|
+
|
|
361
|
+
export { type ApiErrorResponse, type BothFormatsRenderResponse, type HtmlRenderResponse, type HttpResponse, type MjmlRenderResponse, PostBrix, PostBrixAuthError, PostBrixAuthenticationError, type PostBrixConfig, PostBrixError, PostBrixForbiddenError, PostBrixLimitError, PostBrixNetworkError, PostBrixNotFoundError, PostBrixRateLimitError, PostBrixResponseError, PostBrixServerError, PostBrixTemplateError, PostBrixTimeoutError, PostBrixValidationError, type RenderFormat, type RenderOptions, type RenderRequest, type RenderResponse, type RenderResult, SDK_VERSION, VERSION };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var c=class extends Error{statusCode;requestId;code;retryable;details;cause;constructor(e,t){super(e),this.name="PostBrixError",this.statusCode=t?.statusCode,this.requestId=t?.requestId,this.code=t?.code??"POSTBRIX_ERROR",this.retryable=t?.retryable??false,this.details=t?.details,this.cause=t?.cause,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),Object.setPrototypeOf(this,new.target.prototype);}},y=class r extends c{constructor(e,t){super(`Request timed out after ${e}ms`,{code:"POSTBRIX_TIMEOUT",requestId:t?.requestId,retryable:true,cause:t?.cause}),this.name="PostBrixTimeoutError",Object.setPrototypeOf(this,r.prototype);}},b=class r extends c{constructor(e,t){super(e,{code:"POSTBRIX_NETWORK_ERROR",requestId:t?.requestId,retryable:t?.retryable??true,cause:t?.cause}),this.name="PostBrixNetworkError",Object.setPrototypeOf(this,r.prototype);}},d=class r extends c{constructor(e,t){super(e,{statusCode:t?.statusCode??400,requestId:t?.requestId,code:t?.code??"POSTBRIX_VALIDATION_ERROR",retryable:false,details:t?.details,cause:t?.cause}),this.name="PostBrixValidationError",Object.setPrototypeOf(this,r.prototype);}},E=class r extends c{constructor(e,t){let n=t?.statusCode??401,o=n===403?"FORBIDDEN":"POSTBRIX_AUTH_ERROR";super(e,{statusCode:n,requestId:t?.requestId,code:t?.code??o,retryable:false,details:t?.details,cause:t?.cause}),this.name="PostBrixAuthError",Object.setPrototypeOf(this,r.prototype);}},R=class r extends c{retryAfter;constructor(e,t){super(e,{statusCode:t?.statusCode??429,requestId:t?.requestId,code:t?.code??"POSTBRIX_LIMIT_ERROR",retryable:true,details:t?.details,cause:t?.cause}),this.name="PostBrixLimitError",this.retryAfter=t?.retryAfter,Object.setPrototypeOf(this,r.prototype);}},h=class r extends c{constructor(e,t){super(e,{statusCode:t?.statusCode??404,requestId:t?.requestId,code:t?.code??"POSTBRIX_TEMPLATE_ERROR",retryable:false,details:t?.details,cause:t?.cause}),this.name="PostBrixTemplateError",Object.setPrototypeOf(this,r.prototype);}},l=class r extends c{constructor(e,t){super(e,{statusCode:t?.statusCode??502,requestId:t?.requestId,code:t?.code??"INVALID_RESPONSE",retryable:false,details:t?.details,cause:t?.cause}),this.name="PostBrixResponseError",Object.setPrototypeOf(this,r.prototype);}},g=class r extends E{constructor(e,t){super(e,{statusCode:t?.statusCode??403,requestId:t?.requestId,code:t?.code??"FORBIDDEN",details:t?.details,cause:t?.cause}),this.name="PostBrixForbiddenError",Object.setPrototypeOf(this,r.prototype);}},f=class r extends c{constructor(e,t){super(e,{statusCode:t?.statusCode??500,requestId:t?.requestId,code:t?.code??"SERVER_ERROR",retryable:t?.retryable??true,details:t?.details,cause:t?.cause}),this.name="PostBrixServerError",Object.setPrototypeOf(this,r.prototype);}},_=E,j=R,L=h;function P(r,e,t){let n=typeof t=="string"?{requestId:t}:t,{requestId:o,code:a,details:s,retryAfter:i}=n??{};switch(r){case 400:return new d(e,{statusCode:r,requestId:o,code:a??"VALIDATION_ERROR",details:s});case 401:return new E(e,{statusCode:r,requestId:o,code:a??"INVALID_API_KEY",details:s});case 403:return new g(e,{statusCode:r,requestId:o,code:a??"FORBIDDEN",details:s});case 404:return new h(e,{statusCode:r,requestId:o,code:a??"TEMPLATE_NOT_FOUND",details:s});case 429:return new R(e,{statusCode:r,requestId:o,code:a??"RATE_LIMIT_EXCEEDED",retryAfter:i,details:s});case 500:return new f(e,{statusCode:500,requestId:o,code:a??"INTERNAL_SERVER_ERROR",retryable:true,details:s});case 502:return new f(e,{statusCode:502,requestId:o,code:a??"BAD_GATEWAY",retryable:true,details:s});case 503:return new f(e,{statusCode:503,requestId:o,code:a??"SERVICE_UNAVAILABLE",retryable:true,details:s});case 504:return new f(e,{statusCode:504,requestId:o,code:a??"GATEWAY_TIMEOUT",retryable:true,details:s});default:return r>=500?new f(e,{statusCode:r,requestId:o,code:a??"SERVER_ERROR",retryable:true,details:s}):new c(e,{statusCode:r,requestId:o,code:a??"POSTBRIX_ERROR",retryable:false,details:s})}}function T(r,e){if(typeof r!="string"||r.trim().length===0)throw new d(`${e} must be a non-empty string`)}function C(r,e){if(typeof r!="number"||Number.isNaN(r)||r<=0)throw new d(`${e} must be greater than 0`)}function S(r,e){if(typeof r!="number"||Number.isNaN(r)||r<0||!Number.isInteger(r))throw new d(`${e} must be a non-negative integer`)}function q(r){let n=500*Math.pow(2,r),o=Math.random()*100;return Math.min(n+o,1e4)}function B(r){if(!r)return null;let e=r.trim();if(!e)return null;let t=Number(e);if(!Number.isNaN(t)&&t>=0)return t*1e3;let n=Date.parse(e);if(!Number.isNaN(n)){let o=n-Date.now();return Math.max(0,o)}return null}function D(r){return [429,502,503,504].includes(r)}function N(r){return new Promise(e=>setTimeout(e,r))}function A(r,e){let t=r.replace(/bearer\s+[a-zA-Z0-9_.-]+/gi,"Bearer [REDACTED]").replace(/pb_(?:live|test)_[a-zA-Z0-9_-]+/gi,"pb_[REDACTED]").replace(/sk_(?:live|test)_[a-zA-Z0-9_-]+/gi,"sk_[REDACTED]").replace(/api[_-]?key[=:]\s*[a-zA-Z0-9_.-]+/gi,"apiKey=[REDACTED]").replace(/authorization[=:]\s*[^\s,]+/gi,"authorization=[REDACTED]");return e&&e.trim().length>0&&(t=t.split(e.trim()).join("[REDACTED]")),t}function k(r){if(!r)return {};if(typeof r!="object"||Array.isArray(r))throw new d("Template variables must be an object with string keys");let e=new WeakSet;function t(n,o){if(n==null)return n;if(typeof n=="bigint")throw new d(`Unsupported variable type 'bigint' at '${o}'. Template variables must be JSON-serializable.`);if(typeof n=="function")throw new d(`Unsupported variable type 'function' at '${o}'. Template variables must be JSON-serializable.`);if(typeof n=="symbol")throw new d(`Unsupported variable type 'symbol' at '${o}'. Template variables must be JSON-serializable.`);if(typeof n!="object")return n;if(n instanceof Map||n instanceof Set)throw new d(`Unsupported variable collection '${n.constructor.name}' at '${o}'. Template variables must be JSON-serializable.`);if(e.has(n))throw new d(`Circular reference detected at '${o}'. Template variables must be JSON-serializable.`);if(e.add(n),Array.isArray(n)){let i=n.map((u,m)=>t(u,`${o}[${m}]`));return e.delete(n),i}if(n instanceof Date)return e.delete(n),new Date(n.getTime());if(n instanceof RegExp)return e.delete(n),new RegExp(n.source,n.flags);let a=n,s={};for(let i of Object.keys(a)){let u=o?`${o}.${i}`:i;s[i]=t(a[i],u);}return e.delete(n),s}return t(r,"")}var I="1.0.0",U=I;var O=class r{apiKey;baseURL;timeout;maxRetries;static DEFAULT_BASE_URL="https://api.postbrix.com/v1";static DEFAULT_TIMEOUT=3e4;static DEFAULT_MAX_RETRIES=2;constructor(e){this.validateConfig(e),this.apiKey=e.apiKey.trim(),this.baseURL=(e.baseURL??r.DEFAULT_BASE_URL).trim().replace(/\/+$/,""),this.timeout=e.timeout??r.DEFAULT_TIMEOUT,this.maxRetries=e.maxRetries??r.DEFAULT_MAX_RETRIES;}validateConfig(e){if(!e||typeof e!="object")throw new d("Configuration object is required. Usage: new PostBrix({ apiKey: 'pb_live_...' })");T(e.apiKey,"apiKey"),e.baseURL!==void 0&&e.baseURL!==null&&T(e.baseURL,"baseURL"),e.timeout!==void 0&&e.timeout!==null&&C(e.timeout,"timeout"),e.maxRetries!==void 0&&e.maxRetries!==null&&S(e.maxRetries,"maxRetries");}getConfig(){let e=this.apiKey.length>8?`${this.apiKey.slice(0,7)}...[REDACTED]`:"[REDACTED]";return Object.freeze({baseURL:this.baseURL,timeout:this.timeout,maxRetries:this.maxRetries,apiKey:e})}async render(e,t,n){T(e,"templateId");let o=e.trim();if(o.length===0)throw new d("templateId must be a non-empty string");let a=n?.format??"html";if(a!=="html"&&a!=="mjml"&&a!=="both")throw new d(`Invalid format '${String(a)}'. Must be 'html', 'mjml', or 'both'.`);let s=k(t),i=`${this.baseURL}/templates/${encodeURIComponent(o)}/render`,u={variables:s,format:a};return await this.request(i,{method:"POST",body:JSON.stringify(u)},a)}async request(e,t={},n="html"){let o;for(let a=0;a<=this.maxRetries;a++)try{return await this.executeRequest(e,t,n)}catch(s){if(o=s instanceof Error?s:new Error(String(s)),!this.isRetryableError(s)||a===this.maxRetries)throw s;let i;s instanceof R&&typeof s.retryAfter=="number"&&s.retryAfter>=0?i=s.retryAfter:i=q(a),await N(i);}throw o??new c("Request failed after max retries")}async executeRequest(e,t={},n="html"){let o=new AbortController,a=setTimeout(()=>o.abort(),this.timeout);try{let s=this.buildHeaders(),i=t.method??"GET",u=await fetch(e,{method:i,headers:s,body:t.body,signal:o.signal});return u.ok?await this.handleSuccessResponse(u,n):await this.handleErrorResponse(u)}catch(s){if(s instanceof Error&&s.name==="AbortError"||typeof DOMException<"u"&&s instanceof DOMException&&s.name==="AbortError")throw new y(this.timeout,{cause:s instanceof Error?s:void 0});if(s instanceof c)throw s;let i=s instanceof Error?s.message:String(s),u=A(i,this.apiKey);throw new b(`Network request failed: ${u}`,{cause:s instanceof Error?s:void 0,retryable:true})}finally{clearTimeout(a);}}buildHeaders(){return {Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json",Accept:"application/json","User-Agent":`postbrix-sdk/${I}`}}async handleSuccessResponse(e,t){let n=e.headers.get("x-request-id")??void 0,o;try{o=await e.json();}catch(p){throw new l("Failed to parse response body as JSON",{statusCode:e.status,requestId:n,cause:p instanceof Error?p:void 0})}if(!o||typeof o!="object")throw new l("Server returned an invalid response structure",{statusCode:e.status,requestId:n});let a=o,s=a.data&&typeof a.data=="object"?a.data:a,i=typeof s.html=="string"?s.html:void 0,u=typeof s.mjml=="string"?s.mjml:void 0,m=typeof s.format=="string"?s.format:t;if(m==="html"){if(typeof i!="string")throw new l("Server response missing required 'html' string field for format 'html'",{statusCode:e.status,requestId:n,details:o});return {format:"html",html:i}}if(m==="mjml"){if(typeof u!="string")throw new l("Server response missing required 'mjml' string field for format 'mjml'",{statusCode:e.status,requestId:n,details:o});return {format:"mjml",mjml:u}}if(m==="both"){if(typeof i!="string"||typeof u!="string")throw new l("Server response missing required 'html' or 'mjml' string fields for format 'both'",{statusCode:e.status,requestId:n,details:o});return {format:"both",html:i,mjml:u}}throw new l(`Unexpected response format '${String(m)}'`,{statusCode:e.status,requestId:n,details:o})}async handleErrorResponse(e){let t=e.headers.get("x-request-id")??void 0,n=e.headers.get("retry-after"),o=B(n)??void 0,a=null,s=null;try{a=await e.json();}catch{try{s=await e.text();}catch{}}let i=t??a?.requestId??void 0,u=a?.message??(typeof a?.error=="string"?a.error:void 0);u||(s&&s.trim().length>0&&!s.trim().startsWith("<")?u=s.trim():u=`HTTP ${e.status}: ${e.statusText||"Error"}`);let m=A(u,this.apiKey),p=a?.code;throw !p&&typeof a?.error=="string"&&/^[A-Z0-9_-]+$/.test(a.error)&&(p=a.error),P(e.status,m,{requestId:i,code:p,details:a?.details,retryAfter:o})}isRetryableError(e){if(e instanceof c){if(e.retryable)return true;let t=e.statusCode;return t!==void 0&&D(t)}return e instanceof y||e instanceof b}};
|
|
2
|
+
export{O as PostBrix,E as PostBrixAuthError,_ as PostBrixAuthenticationError,c as PostBrixError,g as PostBrixForbiddenError,R as PostBrixLimitError,b as PostBrixNetworkError,L as PostBrixNotFoundError,j as PostBrixRateLimitError,l as PostBrixResponseError,f as PostBrixServerError,h as PostBrixTemplateError,y as PostBrixTimeoutError,d as PostBrixValidationError,I as SDK_VERSION,U as VERSION};
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@postbrix/sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official PostBrix SDK for Node.js, Bun, Deno, Cloudflare Workers, and Vercel Edge Runtime",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.mjs",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/index.d.mts",
|
|
14
|
+
"default": "./dist/index.mjs"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.cjs"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"keywords": [
|
|
31
|
+
"postbrix",
|
|
32
|
+
"email",
|
|
33
|
+
"templates",
|
|
34
|
+
"api",
|
|
35
|
+
"sdk",
|
|
36
|
+
"typescript",
|
|
37
|
+
"javascript"
|
|
38
|
+
],
|
|
39
|
+
"author": "PostBrix Team <support@postbrix.com>",
|
|
40
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/postbrix/postbrix-sdk.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/postbrix/postbrix-sdk/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/postbrix/postbrix-sdk",
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=18.0.0"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsup",
|
|
54
|
+
"clean": "rm -rf dist",
|
|
55
|
+
"lint": "eslint src --max-warnings 0",
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"test": "VITE_CJS_IGNORE_WARNING=true vitest run",
|
|
58
|
+
"test:watch": "VITE_CJS_IGNORE_WARNING=true vitest",
|
|
59
|
+
"test:coverage": "VITE_CJS_IGNORE_WARNING=true vitest run --coverage",
|
|
60
|
+
"test:integration": "VITE_CJS_IGNORE_WARNING=true vitest run tests/integration.test.ts",
|
|
61
|
+
"prepublishOnly": "npm run clean && npm run lint && npm run typecheck && npm run test && npm run build"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/node": "^20.11.0",
|
|
65
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
66
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
67
|
+
"@vitest/coverage-v8": "^1.1.0",
|
|
68
|
+
"eslint": "^8.56.0",
|
|
69
|
+
"prettier": "^3.1.1",
|
|
70
|
+
"tsup": "^8.0.1",
|
|
71
|
+
"typescript": "^5.3.3",
|
|
72
|
+
"vitest": "^1.1.0"
|
|
73
|
+
}
|
|
74
|
+
}
|