1
1
interface QueryOptions {
2
2
path : `v1/${string } `;
3
3
method ?: "GET" | "POST" | "PUT" ;
4
- payload ?: Record < string , any > ;
4
+ payload ?: Record < string , unknown > ;
5
5
params ?: Record < string , string > ;
6
6
}
7
7
@@ -14,8 +14,6 @@ interface ApiKeyErrorResponse {
14
14
error : "Invalid API key" ;
15
15
}
16
16
17
- type ApiKeyResponse = ApiKeySuccessResponse | ApiKeyErrorResponse ;
18
-
19
17
interface ContactSuccessResponse {
20
18
success : true ;
21
19
/** The ID of the contact. */
@@ -188,13 +186,19 @@ interface TransactionalEmail {
188
186
189
187
interface ContactProperty {
190
188
/**
189
+ * The property's name.
190
+ */
191
+ key : string ;
192
+ /**
193
+ * The human-friendly label for this property.
191
194
*/
192
195
label : string ;
193
196
/**
194
197
* The type of property.
195
198
*/
196
199
type : "string" | "number" | "boolean" | "date" ;
197
200
}
201
+
198
202
interface ListTransactionalsResponse {
199
203
pagination : PaginationData ;
200
204
data : TransactionalEmail [ ] ;
@@ -213,13 +217,29 @@ class RateLimitExceededError extends Error {
213
217
214
218
class APIError extends Error {
215
219
statusCode : number ;
216
- json : ErrorResponse | TransactionalError | TransactionalNestedError ;
217
- json : ErrorResponse | TransactionalError | TransactionalNestedError
220
+ json :
221
+ | ErrorResponse
222
+ | TransactionalError
223
+ | TransactionalNestedError
224
+ | ApiKeyErrorResponse ;
225
+ constructor (
226
+ statusCode : number ,
227
+ json :
228
+ | ErrorResponse
229
+ | TransactionalError
230
+ | TransactionalNestedError
231
+ | ApiKeyErrorResponse
218
232
) {
219
233
let message : string | undefined ;
220
- if ( "error" in json && json . error ?. message ) {
234
+ if (
235
+ "error" in json &&
236
+ typeof json . error === "object" &&
237
+ json . error ?. message
238
+ ) {
221
239
message = json . error . message ;
222
- } else if ( "message" in json ) {
240
+ } else if ( "error" in json && typeof json . error === "string" ) {
241
+ message = json . error ;
242
+ } else if ( "message" in json && typeof json . message === "string" ) {
223
243
message = json . message ;
224
244
}
225
245
super ( `${ statusCode } ${ message ? ` - ${ message } ` : "" } ` ) ;
@@ -234,6 +254,13 @@ class APIError extends Error {
234
254
}
235
255
}
236
256
257
+ class ValidationError extends Error {
258
+ constructor ( message : string ) {
259
+ super ( message ) ;
260
+ this . name = "ValidationError" ;
261
+ }
262
+ }
263
+
237
264
class LoopsClient {
238
265
apiKey : string ;
239
266
apiRoot = "https://app.loops.so/api/" ;
@@ -251,20 +278,20 @@ class LoopsClient {
251
278
* @param {Object } params.payload Payload for PUT and POST requests
252
279
* @param {Object } params.params URL query parameters
253
280
*/
254
- private async _makeQuery ( {
281
+ private async _makeQuery < T > ( {
255
282
path,
256
283
method = "GET" ,
257
284
payload,
258
285
params,
259
- } : QueryOptions ) {
286
+ } : QueryOptions ) : Promise < T > {
260
287
const headers = new Headers ( ) ;
261
288
headers . set ( "Authorization" , `Bearer ${ this . apiKey } ` ) ;
262
289
headers . set ( "Content-Type" , "application/json" ) ;
263
290
264
291
const url = new URL ( path , this . apiRoot ) ;
265
292
if ( params && method === "GET" ) {
266
293
Object . entries ( params ) . forEach ( ( [ key , value ] ) =>
267
- url . searchParams . append ( key , value )
294
+ url . searchParams . append ( key , value as string )
268
295
) ;
269
296
}
270
297
@@ -305,9 +332,9 @@ class LoopsClient {
305
332
*
306
333
* @see https://loops.so/docs/api-reference/api-key
307
334
*
308
- * @returns {Object } Success or error message (JSON)
335
+ * @returns {Object } Success response (JSON)
309
336
*/
310
- async testApiKey ( ) : Promise < ApiKeyResponse > {
337
+ async testApiKey ( ) : Promise < ApiKeySuccessResponse > {
311
338
return this . _makeQuery ( {
312
339
path : "v1/api-key" ,
313
340
} ) ;
@@ -322,7 +349,7 @@ class LoopsClient {
322
349
*
323
350
* @see https://loops.so/docs/api-reference/create-contact
324
351
*
325
- * @returns {Object } Contact record or error response (JSON)
352
+ * @returns {Object } Contact record (JSON)
326
353
*/
327
354
async createContact (
328
355
email : string ,
@@ -346,7 +373,7 @@ class LoopsClient {
346
373
*
347
374
* @see https://loops.so/docs/api-reference/update-contact
348
375
*
349
- * @returns {Object } Contact record or error response (JSON)
376
+ * @returns {Object } Contact record (JSON)
350
377
*/
351
378
async updateContact (
352
379
email : string ,
@@ -379,11 +406,15 @@ class LoopsClient {
379
406
email ?: string ;
380
407
userId ?: string ;
381
408
} ) : Promise < Contact [ ] > {
382
- if ( email && userId ) throw "Only one parameter is permitted." ;
409
+ if ( email && userId )
410
+ throw new ValidationError ( "Only one parameter is permitted." ) ;
411
+ if ( ! email && ! userId )
412
+ throw new ValidationError (
413
+ "You must provide an `email` or `userId` value."
414
+ ) ;
383
415
const params : { email ?: string ; userId ?: string } = { } ;
384
416
if ( email ) params [ "email" ] = email ;
385
417
else if ( userId ) params [ "userId" ] = userId ;
386
- else throw "You must provide an `email` or `userId` value." ;
387
418
return this . _makeQuery ( {
388
419
path : "v1/contacts/find" ,
389
420
params,
@@ -399,7 +430,7 @@ class LoopsClient {
399
430
*
400
431
* @see https://loops.so/docs/api-reference/delete-contact
401
432
*
402
- * @returns {Object } Confirmation or error response (JSON)
433
+ * @returns {Object } Confirmation (JSON)
403
434
*/
404
435
async deleteContact ( {
405
436
email,
@@ -408,11 +439,15 @@ class LoopsClient {
408
439
email ?: string ;
409
440
userId ?: string ;
410
441
} ) : Promise < DeleteSuccessResponse > {
411
- if ( email && userId ) throw "Only one parameter is permitted." ;
442
+ if ( email && userId )
443
+ throw new ValidationError ( "Only one parameter is permitted." ) ;
444
+ if ( ! email && ! userId )
445
+ throw new ValidationError (
446
+ "You must provide an `email` or `userId` value."
447
+ ) ;
412
448
const payload : { email ?: string ; userId ?: string } = { } ;
413
449
if ( email ) payload [ "email" ] = email ;
414
450
else if ( userId ) payload [ "userId" ] = userId ;
415
- else throw "You must provide an `email` or `userId` value." ;
416
451
return this . _makeQuery ( {
417
452
path : "v1/contacts/delete" ,
418
453
method : "POST" ,
@@ -428,7 +463,7 @@ class LoopsClient {
428
463
*
429
464
* @see https://loops.so/docs/api-reference/create-contact-property
430
465
*
431
- * @returns {Object } Contact property record or error response (JSON)
466
+ * @returns {Object } Contact property record (JSON)
432
467
*/
433
468
async createContactProperty (
434
469
name : string ,
@@ -540,7 +575,7 @@ class LoopsClient {
540
575
*
541
576
* @see https://loops.so/docs/api-reference/send-transactional-email
542
577
*
543
- * @returns {Object } Confirmation or error response (JSON)
578
+ * @returns {Object } Confirmation (JSON)
544
579
*/
545
580
async sendTransactionalEmail ( {
546
581
transactionalId,
@@ -598,4 +633,29 @@ class LoopsClient {
598
633
}
599
634
}
600
635
601
- export { LoopsClient , RateLimitExceededError , APIError } ;
636
+ export {
637
+ LoopsClient ,
638
+ RateLimitExceededError ,
639
+ APIError ,
640
+ ValidationError ,
641
+ ApiKeySuccessResponse ,
642
+ ApiKeyErrorResponse ,
643
+ ContactSuccessResponse ,
644
+ DeleteSuccessResponse ,
645
+ ErrorResponse ,
646
+ Contact ,
647
+ ContactProperty ,
648
+ ContactPropertySuccessResponse ,
649
+ EventSuccessResponse ,
650
+ TransactionalSuccess ,
651
+ TransactionalError ,
652
+ TransactionalNestedError ,
653
+ ContactProperties ,
654
+ EventProperties ,
655
+ TransactionalVariables ,
656
+ TransactionalAttachment ,
657
+ MailingList ,
658
+ PaginationData ,
659
+ TransactionalEmail ,
660
+ ListTransactionalsResponse ,
661
+ } ;
0 commit comments