@devwithbobby/loops 0.1.12 → 0.1.14
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/client/index.d.ts +305 -44
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +21 -32
- package/dist/component/convex.config.d.ts +1 -1
- package/dist/component/convex.config.d.ts.map +1 -1
- package/dist/component/convex.config.js +1 -1
- package/dist/component/helpers.d.ts +7 -0
- package/dist/component/helpers.d.ts.map +1 -0
- package/dist/component/helpers.js +30 -0
- package/dist/component/http.d.ts +3 -0
- package/dist/component/http.d.ts.map +1 -0
- package/dist/component/http.js +268 -0
- package/dist/component/lib.d.ts +237 -22
- package/dist/component/lib.d.ts.map +1 -1
- package/dist/component/lib.js +91 -143
- package/dist/component/schema.d.ts +66 -1
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/tables/contacts.d.ts +123 -1
- package/dist/component/tables/contacts.d.ts.map +1 -1
- package/dist/component/tables/emailOperations.d.ts +151 -1
- package/dist/component/tables/emailOperations.d.ts.map +1 -1
- package/dist/component/tables/emailOperations.js +1 -6
- package/dist/component/validators.d.ts +20 -3
- package/dist/component/validators.d.ts.map +1 -1
- package/dist/types.d.ts +97 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/utils.d.ts +186 -3
- package/dist/utils.d.ts.map +1 -1
- package/package.json +101 -101
- package/src/client/index.ts +40 -52
- package/src/component/_generated/api.d.ts +3 -11
- package/src/component/convex.config.ts +7 -2
- package/src/component/helpers.ts +44 -0
- package/src/component/http.ts +304 -0
- package/src/component/lib.ts +189 -204
- package/src/component/tables/contacts.ts +0 -1
- package/src/component/tables/emailOperations.ts +1 -7
- package/src/component/validators.ts +0 -1
- package/src/types.ts +168 -0
- package/src/client/types.ts +0 -64
package/dist/component/lib.js
CHANGED
|
@@ -1,26 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { internalLib } from "../types";
|
|
2
3
|
import { za, zm, zq } from "../utils.js";
|
|
3
|
-
import {
|
|
4
|
+
import { loopsFetch, sanitizeLoopsError } from "./helpers";
|
|
4
5
|
import { contactValidator } from "./validators.js";
|
|
5
|
-
const LOOPS_API_BASE_URL = "https://app.loops.so/api/v1";
|
|
6
|
-
/**
|
|
7
|
-
* Sanitize error messages to avoid leaking sensitive information
|
|
8
|
-
*/
|
|
9
|
-
const sanitizeError = (status, errorText) => {
|
|
10
|
-
if (status === 401 || status === 403) {
|
|
11
|
-
return new Error("Authentication failed. Please check your API key.");
|
|
12
|
-
}
|
|
13
|
-
if (status === 404) {
|
|
14
|
-
return new Error("Resource not found.");
|
|
15
|
-
}
|
|
16
|
-
if (status === 429) {
|
|
17
|
-
return new Error("Rate limit exceeded. Please try again later.");
|
|
18
|
-
}
|
|
19
|
-
if (status >= 500) {
|
|
20
|
-
return new Error("Loops service error. Please try again later.");
|
|
21
|
-
}
|
|
22
|
-
return new Error(`Loops API error (${status}). Please try again.`);
|
|
23
|
-
};
|
|
24
6
|
/**
|
|
25
7
|
* Internal mutation to store/update a contact in the database
|
|
26
8
|
*/
|
|
@@ -111,21 +93,14 @@ export const logEmailOperation = zm({
|
|
|
111
93
|
email: args.email,
|
|
112
94
|
timestamp: Date.now(),
|
|
113
95
|
success: args.success,
|
|
96
|
+
actorId: args.actorId,
|
|
97
|
+
transactionalId: args.transactionalId,
|
|
98
|
+
campaignId: args.campaignId,
|
|
99
|
+
loopId: args.loopId,
|
|
100
|
+
eventName: args.eventName,
|
|
101
|
+
messageId: args.messageId,
|
|
102
|
+
metadata: args.metadata,
|
|
114
103
|
};
|
|
115
|
-
if (args.actorId)
|
|
116
|
-
operationData.actorId = args.actorId;
|
|
117
|
-
if (args.transactionalId)
|
|
118
|
-
operationData.transactionalId = args.transactionalId;
|
|
119
|
-
if (args.campaignId)
|
|
120
|
-
operationData.campaignId = args.campaignId;
|
|
121
|
-
if (args.loopId)
|
|
122
|
-
operationData.loopId = args.loopId;
|
|
123
|
-
if (args.eventName)
|
|
124
|
-
operationData.eventName = args.eventName;
|
|
125
|
-
if (args.messageId)
|
|
126
|
-
operationData.messageId = args.messageId;
|
|
127
|
-
if (args.metadata)
|
|
128
|
-
operationData.metadata = args.metadata;
|
|
129
104
|
await ctx.db.insert("emailOperations", operationData);
|
|
130
105
|
},
|
|
131
106
|
});
|
|
@@ -244,7 +219,12 @@ export const listContacts = zq({
|
|
|
244
219
|
// Sort by createdAt (newest first)
|
|
245
220
|
allContacts.sort((a, b) => b.createdAt - a.createdAt);
|
|
246
221
|
const total = allContacts.length;
|
|
247
|
-
const paginatedContacts = allContacts
|
|
222
|
+
const paginatedContacts = allContacts
|
|
223
|
+
.slice(args.offset, args.offset + args.limit)
|
|
224
|
+
.map((contact) => ({
|
|
225
|
+
...contact,
|
|
226
|
+
subscribed: contact.subscribed ?? true, // Ensure subscribed is always boolean
|
|
227
|
+
}));
|
|
248
228
|
const hasMore = args.offset + args.limit < total;
|
|
249
229
|
return {
|
|
250
230
|
contacts: paginatedContacts,
|
|
@@ -270,36 +250,22 @@ export const addContact = za({
|
|
|
270
250
|
id: z.string().optional(),
|
|
271
251
|
}),
|
|
272
252
|
handler: async (ctx, args) => {
|
|
273
|
-
const
|
|
253
|
+
const createResponse = await loopsFetch(args.apiKey, "/contacts/create", {
|
|
274
254
|
method: "POST",
|
|
275
|
-
|
|
276
|
-
Authorization: `Bearer ${args.apiKey}`,
|
|
277
|
-
"Content-Type": "application/json",
|
|
278
|
-
},
|
|
279
|
-
body: JSON.stringify(args.contact),
|
|
255
|
+
json: args.contact,
|
|
280
256
|
});
|
|
281
|
-
if (!
|
|
282
|
-
const errorText = await
|
|
283
|
-
if (
|
|
257
|
+
if (!createResponse.ok) {
|
|
258
|
+
const errorText = await createResponse.text();
|
|
259
|
+
if (createResponse.status === 409) {
|
|
284
260
|
console.log(`Contact ${args.contact.email} already exists, updating instead`);
|
|
285
|
-
const findResponse = await
|
|
286
|
-
method: "GET",
|
|
287
|
-
headers: {
|
|
288
|
-
Authorization: `Bearer ${args.apiKey}`,
|
|
289
|
-
"Content-Type": "application/json",
|
|
290
|
-
},
|
|
291
|
-
});
|
|
261
|
+
const findResponse = await loopsFetch(args.apiKey, `/contacts/find?email=${encodeURIComponent(args.contact.email)}`, { method: "GET" });
|
|
292
262
|
if (!findResponse.ok) {
|
|
293
263
|
const findErrorText = await findResponse.text();
|
|
294
264
|
console.error(`Failed to find existing contact [${findResponse.status}]:`, findErrorText);
|
|
295
265
|
}
|
|
296
|
-
const updateResponse = await
|
|
266
|
+
const updateResponse = await loopsFetch(args.apiKey, "/contacts/update", {
|
|
297
267
|
method: "PUT",
|
|
298
|
-
|
|
299
|
-
Authorization: `Bearer ${args.apiKey}`,
|
|
300
|
-
"Content-Type": "application/json",
|
|
301
|
-
},
|
|
302
|
-
body: JSON.stringify({
|
|
268
|
+
json: {
|
|
303
269
|
email: args.contact.email,
|
|
304
270
|
firstName: args.contact.firstName,
|
|
305
271
|
lastName: args.contact.lastName,
|
|
@@ -307,12 +273,12 @@ export const addContact = za({
|
|
|
307
273
|
source: args.contact.source,
|
|
308
274
|
subscribed: args.contact.subscribed,
|
|
309
275
|
userGroup: args.contact.userGroup,
|
|
310
|
-
}
|
|
276
|
+
},
|
|
311
277
|
});
|
|
312
278
|
if (!updateResponse.ok) {
|
|
313
279
|
const updateErrorText = await updateResponse.text();
|
|
314
280
|
console.error(`Loops API error [${updateResponse.status}]:`, updateErrorText);
|
|
315
|
-
throw
|
|
281
|
+
throw sanitizeLoopsError(updateResponse.status, updateErrorText);
|
|
316
282
|
}
|
|
317
283
|
// Get contact ID if available
|
|
318
284
|
let contactId;
|
|
@@ -321,7 +287,7 @@ export const addContact = za({
|
|
|
321
287
|
contactId = findData.id;
|
|
322
288
|
}
|
|
323
289
|
// Store/update in our database
|
|
324
|
-
await ctx.runMutation(
|
|
290
|
+
await ctx.runMutation(internalLib.storeContact, {
|
|
325
291
|
email: args.contact.email,
|
|
326
292
|
firstName: args.contact.firstName,
|
|
327
293
|
lastName: args.contact.lastName,
|
|
@@ -336,13 +302,12 @@ export const addContact = za({
|
|
|
336
302
|
id: contactId,
|
|
337
303
|
};
|
|
338
304
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
throw sanitizeError(response.status, errorText);
|
|
305
|
+
console.error(`Loops API error [${createResponse.status}]:`, errorText);
|
|
306
|
+
throw sanitizeLoopsError(createResponse.status, errorText);
|
|
342
307
|
}
|
|
343
308
|
// Contact was created successfully
|
|
344
|
-
const data = (await
|
|
345
|
-
await ctx.runMutation(
|
|
309
|
+
const data = (await createResponse.json());
|
|
310
|
+
await ctx.runMutation(internalLib.storeContact, {
|
|
346
311
|
email: args.contact.email,
|
|
347
312
|
firstName: args.contact.firstName,
|
|
348
313
|
lastName: args.contact.lastName,
|
|
@@ -377,13 +342,9 @@ export const updateContact = za({
|
|
|
377
342
|
success: z.boolean(),
|
|
378
343
|
}),
|
|
379
344
|
handler: async (ctx, args) => {
|
|
380
|
-
const response = await
|
|
345
|
+
const response = await loopsFetch(args.apiKey, "/contacts/update", {
|
|
381
346
|
method: "PUT",
|
|
382
|
-
|
|
383
|
-
Authorization: `Bearer ${args.apiKey}`,
|
|
384
|
-
"Content-Type": "application/json",
|
|
385
|
-
},
|
|
386
|
-
body: JSON.stringify({
|
|
347
|
+
json: {
|
|
387
348
|
email: args.email,
|
|
388
349
|
dataVariables: args.dataVariables,
|
|
389
350
|
firstName: args.firstName,
|
|
@@ -392,14 +353,14 @@ export const updateContact = za({
|
|
|
392
353
|
source: args.source,
|
|
393
354
|
subscribed: args.subscribed,
|
|
394
355
|
userGroup: args.userGroup,
|
|
395
|
-
}
|
|
356
|
+
},
|
|
396
357
|
});
|
|
397
358
|
if (!response.ok) {
|
|
398
359
|
const errorText = await response.text();
|
|
399
360
|
console.error(`Loops API error [${response.status}]:`, errorText);
|
|
400
|
-
throw
|
|
361
|
+
throw sanitizeLoopsError(response.status, errorText);
|
|
401
362
|
}
|
|
402
|
-
await ctx.runMutation(
|
|
363
|
+
await ctx.runMutation(internalLib.storeContact, {
|
|
403
364
|
email: args.email,
|
|
404
365
|
firstName: args.firstName,
|
|
405
366
|
lastName: args.lastName,
|
|
@@ -426,31 +387,27 @@ export const sendTransactional = za({
|
|
|
426
387
|
messageId: z.string().optional(),
|
|
427
388
|
}),
|
|
428
389
|
handler: async (ctx, args) => {
|
|
429
|
-
const response = await
|
|
390
|
+
const response = await loopsFetch(args.apiKey, "/transactional", {
|
|
430
391
|
method: "POST",
|
|
431
|
-
|
|
432
|
-
Authorization: `Bearer ${args.apiKey}`,
|
|
433
|
-
"Content-Type": "application/json",
|
|
434
|
-
},
|
|
435
|
-
body: JSON.stringify({
|
|
392
|
+
json: {
|
|
436
393
|
transactionalId: args.transactionalId,
|
|
437
394
|
email: args.email,
|
|
438
395
|
dataVariables: args.dataVariables,
|
|
439
|
-
}
|
|
396
|
+
},
|
|
440
397
|
});
|
|
441
398
|
if (!response.ok) {
|
|
442
399
|
const errorText = await response.text();
|
|
443
400
|
console.error(`Loops API error [${response.status}]:`, errorText);
|
|
444
|
-
await ctx.runMutation(
|
|
401
|
+
await ctx.runMutation(internalLib.logEmailOperation, {
|
|
445
402
|
operationType: "transactional",
|
|
446
403
|
email: args.email,
|
|
447
404
|
success: false,
|
|
448
405
|
transactionalId: args.transactionalId,
|
|
449
406
|
});
|
|
450
|
-
throw
|
|
407
|
+
throw sanitizeLoopsError(response.status, errorText);
|
|
451
408
|
}
|
|
452
409
|
const data = (await response.json());
|
|
453
|
-
await ctx.runMutation(
|
|
410
|
+
await ctx.runMutation(internalLib.logEmailOperation, {
|
|
454
411
|
operationType: "transactional",
|
|
455
412
|
email: args.email,
|
|
456
413
|
success: true,
|
|
@@ -476,23 +433,19 @@ export const sendEvent = za({
|
|
|
476
433
|
returns: z.object({
|
|
477
434
|
success: z.boolean(),
|
|
478
435
|
}),
|
|
479
|
-
handler: async (
|
|
480
|
-
const response = await
|
|
436
|
+
handler: async (_ctx, args) => {
|
|
437
|
+
const response = await loopsFetch(args.apiKey, "/events/send", {
|
|
481
438
|
method: "POST",
|
|
482
|
-
|
|
483
|
-
Authorization: `Bearer ${args.apiKey}`,
|
|
484
|
-
"Content-Type": "application/json",
|
|
485
|
-
},
|
|
486
|
-
body: JSON.stringify({
|
|
439
|
+
json: {
|
|
487
440
|
email: args.email,
|
|
488
441
|
eventName: args.eventName,
|
|
489
442
|
eventProperties: args.eventProperties,
|
|
490
|
-
}
|
|
443
|
+
},
|
|
491
444
|
});
|
|
492
445
|
if (!response.ok) {
|
|
493
446
|
const errorText = await response.text();
|
|
494
447
|
console.error(`Loops API error [${response.status}]:`, errorText);
|
|
495
|
-
throw
|
|
448
|
+
throw sanitizeLoopsError(response.status, errorText);
|
|
496
449
|
}
|
|
497
450
|
return { success: true };
|
|
498
451
|
},
|
|
@@ -509,20 +462,16 @@ export const deleteContact = za({
|
|
|
509
462
|
success: z.boolean(),
|
|
510
463
|
}),
|
|
511
464
|
handler: async (ctx, args) => {
|
|
512
|
-
const response = await
|
|
465
|
+
const response = await loopsFetch(args.apiKey, "/contacts/delete", {
|
|
513
466
|
method: "POST",
|
|
514
|
-
|
|
515
|
-
Authorization: `Bearer ${args.apiKey}`,
|
|
516
|
-
"Content-Type": "application/json",
|
|
517
|
-
},
|
|
518
|
-
body: JSON.stringify({ email: args.email }),
|
|
467
|
+
json: { email: args.email },
|
|
519
468
|
});
|
|
520
469
|
if (!response.ok) {
|
|
521
470
|
const errorText = await response.text();
|
|
522
471
|
console.error(`Loops API error [${response.status}]:`, errorText);
|
|
523
|
-
throw
|
|
472
|
+
throw sanitizeLoopsError(response.status, errorText);
|
|
524
473
|
}
|
|
525
|
-
await ctx.runMutation(
|
|
474
|
+
await ctx.runMutation(internalLib.removeContact, {
|
|
526
475
|
email: args.email,
|
|
527
476
|
});
|
|
528
477
|
return { success: true };
|
|
@@ -563,7 +512,7 @@ export const triggerLoop = za({
|
|
|
563
512
|
const eventName = args.eventName || `loop_${args.loopId}`;
|
|
564
513
|
try {
|
|
565
514
|
// Send event to trigger the loop
|
|
566
|
-
await ctx.runAction(
|
|
515
|
+
await ctx.runAction(internalLib.sendEvent, {
|
|
567
516
|
apiKey: args.apiKey,
|
|
568
517
|
email: args.email,
|
|
569
518
|
eventName,
|
|
@@ -573,7 +522,7 @@ export const triggerLoop = za({
|
|
|
573
522
|
},
|
|
574
523
|
});
|
|
575
524
|
// Log as loop operation
|
|
576
|
-
await ctx.runMutation(
|
|
525
|
+
await ctx.runMutation(internalLib.logEmailOperation, {
|
|
577
526
|
operationType: "loop",
|
|
578
527
|
email: args.email,
|
|
579
528
|
success: true,
|
|
@@ -587,13 +536,15 @@ export const triggerLoop = za({
|
|
|
587
536
|
}
|
|
588
537
|
catch (error) {
|
|
589
538
|
// Log failed loop operation
|
|
590
|
-
await ctx.runMutation(
|
|
539
|
+
await ctx.runMutation(internalLib.logEmailOperation, {
|
|
591
540
|
operationType: "loop",
|
|
592
541
|
email: args.email,
|
|
593
542
|
success: false,
|
|
594
543
|
loopId: args.loopId,
|
|
595
544
|
eventName,
|
|
596
|
-
metadata: {
|
|
545
|
+
metadata: {
|
|
546
|
+
error: error instanceof Error ? error.message : String(error),
|
|
547
|
+
},
|
|
597
548
|
});
|
|
598
549
|
throw error;
|
|
599
550
|
}
|
|
@@ -625,28 +576,25 @@ export const findContact = za({
|
|
|
625
576
|
})
|
|
626
577
|
.optional(),
|
|
627
578
|
}),
|
|
628
|
-
handler: async (
|
|
629
|
-
const response = await
|
|
630
|
-
method: "GET",
|
|
631
|
-
headers: {
|
|
632
|
-
Authorization: `Bearer ${args.apiKey}`,
|
|
633
|
-
"Content-Type": "application/json",
|
|
634
|
-
},
|
|
635
|
-
});
|
|
579
|
+
handler: async (_ctx, args) => {
|
|
580
|
+
const response = await loopsFetch(args.apiKey, `/contacts/find?email=${encodeURIComponent(args.email)}`, { method: "GET" });
|
|
636
581
|
if (!response.ok) {
|
|
637
582
|
if (response.status === 404) {
|
|
638
583
|
return { success: false, contact: undefined };
|
|
639
584
|
}
|
|
640
585
|
const errorText = await response.text();
|
|
641
586
|
console.error(`Loops API error [${response.status}]:`, errorText);
|
|
642
|
-
throw
|
|
587
|
+
throw sanitizeLoopsError(response.status, errorText);
|
|
643
588
|
}
|
|
644
589
|
const data = (await response.json());
|
|
645
590
|
// Handle case where Loops returns an array instead of a single object
|
|
646
591
|
let contact = Array.isArray(data) ? data[0] : data;
|
|
647
592
|
// Convert null values to undefined for optional fields (Zod handles undefined but not null in optional())
|
|
648
593
|
if (contact) {
|
|
649
|
-
contact = Object.fromEntries(Object.entries(contact).map(([key, value]) => [
|
|
594
|
+
contact = Object.fromEntries(Object.entries(contact).map(([key, value]) => [
|
|
595
|
+
key,
|
|
596
|
+
value === null ? undefined : value,
|
|
597
|
+
]));
|
|
650
598
|
}
|
|
651
599
|
return {
|
|
652
600
|
success: true,
|
|
@@ -668,11 +616,13 @@ export const batchCreateContacts = za({
|
|
|
668
616
|
success: z.boolean(),
|
|
669
617
|
created: z.number().optional(),
|
|
670
618
|
failed: z.number().optional(),
|
|
671
|
-
results: z
|
|
619
|
+
results: z
|
|
620
|
+
.array(z.object({
|
|
672
621
|
email: z.string(),
|
|
673
622
|
success: z.boolean(),
|
|
674
623
|
error: z.string().optional(),
|
|
675
|
-
}))
|
|
624
|
+
}))
|
|
625
|
+
.optional(),
|
|
676
626
|
}),
|
|
677
627
|
handler: async (ctx, args) => {
|
|
678
628
|
let created = 0;
|
|
@@ -682,7 +632,7 @@ export const batchCreateContacts = za({
|
|
|
682
632
|
for (const contact of args.contacts) {
|
|
683
633
|
try {
|
|
684
634
|
// Use the addContact function which handles create/update logic
|
|
685
|
-
const result = await ctx.runAction(
|
|
635
|
+
const result = await ctx.runAction(internalLib.addContact, {
|
|
686
636
|
apiKey: args.apiKey,
|
|
687
637
|
contact,
|
|
688
638
|
});
|
|
@@ -695,7 +645,7 @@ export const batchCreateContacts = za({
|
|
|
695
645
|
results.push({
|
|
696
646
|
email: contact.email,
|
|
697
647
|
success: false,
|
|
698
|
-
error: "Unknown error"
|
|
648
|
+
error: "Unknown error",
|
|
699
649
|
});
|
|
700
650
|
}
|
|
701
651
|
}
|
|
@@ -729,20 +679,16 @@ export const unsubscribeContact = za({
|
|
|
729
679
|
success: z.boolean(),
|
|
730
680
|
}),
|
|
731
681
|
handler: async (ctx, args) => {
|
|
732
|
-
const response = await
|
|
682
|
+
const response = await loopsFetch(args.apiKey, "/contacts/unsubscribe", {
|
|
733
683
|
method: "POST",
|
|
734
|
-
|
|
735
|
-
Authorization: `Bearer ${args.apiKey}`,
|
|
736
|
-
"Content-Type": "application/json",
|
|
737
|
-
},
|
|
738
|
-
body: JSON.stringify({ email: args.email }),
|
|
684
|
+
json: { email: args.email },
|
|
739
685
|
});
|
|
740
686
|
if (!response.ok) {
|
|
741
687
|
const errorText = await response.text();
|
|
742
688
|
console.error(`Loops API error [${response.status}]:`, errorText);
|
|
743
|
-
throw
|
|
689
|
+
throw sanitizeLoopsError(response.status, errorText);
|
|
744
690
|
}
|
|
745
|
-
await ctx.runMutation(
|
|
691
|
+
await ctx.runMutation(internalLib.storeContact, {
|
|
746
692
|
email: args.email,
|
|
747
693
|
subscribed: false,
|
|
748
694
|
});
|
|
@@ -762,20 +708,16 @@ export const resubscribeContact = za({
|
|
|
762
708
|
success: z.boolean(),
|
|
763
709
|
}),
|
|
764
710
|
handler: async (ctx, args) => {
|
|
765
|
-
const response = await
|
|
711
|
+
const response = await loopsFetch(args.apiKey, "/contacts/resubscribe", {
|
|
766
712
|
method: "POST",
|
|
767
|
-
|
|
768
|
-
Authorization: `Bearer ${args.apiKey}`,
|
|
769
|
-
"Content-Type": "application/json",
|
|
770
|
-
},
|
|
771
|
-
body: JSON.stringify({ email: args.email }),
|
|
713
|
+
json: { email: args.email },
|
|
772
714
|
});
|
|
773
715
|
if (!response.ok) {
|
|
774
716
|
const errorText = await response.text();
|
|
775
717
|
console.error(`Loops API error [${response.status}]:`, errorText);
|
|
776
|
-
throw
|
|
718
|
+
throw sanitizeLoopsError(response.status, errorText);
|
|
777
719
|
}
|
|
778
|
-
await ctx.runMutation(
|
|
720
|
+
await ctx.runMutation(internalLib.storeContact, {
|
|
779
721
|
email: args.email,
|
|
780
722
|
subscribed: true,
|
|
781
723
|
});
|
|
@@ -791,11 +733,13 @@ export const detectRecipientSpam = zq({
|
|
|
791
733
|
timeWindowMs: z.number().default(3600000),
|
|
792
734
|
maxEmailsPerRecipient: z.number().default(10),
|
|
793
735
|
}),
|
|
794
|
-
returns: z.array(z
|
|
736
|
+
returns: z.array(z
|
|
737
|
+
.object({
|
|
795
738
|
email: z.string(),
|
|
796
739
|
count: z.number(),
|
|
797
740
|
timeWindowMs: z.number(),
|
|
798
|
-
})
|
|
741
|
+
})
|
|
742
|
+
.catchall(z.any())),
|
|
799
743
|
handler: async (ctx, args) => {
|
|
800
744
|
const cutoffTime = Date.now() - args.timeWindowMs;
|
|
801
745
|
const operations = await ctx.db
|
|
@@ -867,14 +811,16 @@ export const getEmailStats = zq({
|
|
|
867
811
|
args: z.object({
|
|
868
812
|
timeWindowMs: z.number().default(86400000),
|
|
869
813
|
}),
|
|
870
|
-
returns: z
|
|
814
|
+
returns: z
|
|
815
|
+
.object({
|
|
871
816
|
totalOperations: z.number(),
|
|
872
817
|
successfulOperations: z.number(),
|
|
873
818
|
failedOperations: z.number(),
|
|
874
819
|
operationsByType: z.record(z.string(), z.number()),
|
|
875
820
|
uniqueRecipients: z.number(),
|
|
876
821
|
uniqueActors: z.number(),
|
|
877
|
-
})
|
|
822
|
+
})
|
|
823
|
+
.catchall(z.any()),
|
|
878
824
|
handler: async (ctx, args) => {
|
|
879
825
|
const cutoffTime = Date.now() - args.timeWindowMs;
|
|
880
826
|
const operations = await ctx.db
|
|
@@ -937,7 +883,7 @@ export const detectRapidFirePatterns = zq({
|
|
|
937
883
|
if (!emailGroups.has(op.email)) {
|
|
938
884
|
emailGroups.set(op.email, []);
|
|
939
885
|
}
|
|
940
|
-
emailGroups.get(op.email)
|
|
886
|
+
emailGroups.get(op.email)?.push(op);
|
|
941
887
|
}
|
|
942
888
|
}
|
|
943
889
|
for (const [email, ops] of emailGroups.entries()) {
|
|
@@ -965,7 +911,7 @@ export const detectRapidFirePatterns = zq({
|
|
|
965
911
|
if (!actorGroups.has(op.actorId)) {
|
|
966
912
|
actorGroups.set(op.actorId, []);
|
|
967
913
|
}
|
|
968
|
-
actorGroups.get(op.actorId)
|
|
914
|
+
actorGroups.get(op.actorId)?.push(op);
|
|
969
915
|
}
|
|
970
916
|
}
|
|
971
917
|
for (const [actorId, ops] of actorGroups.entries()) {
|
|
@@ -1000,13 +946,15 @@ export const checkRecipientRateLimit = zq({
|
|
|
1000
946
|
timeWindowMs: z.number(),
|
|
1001
947
|
maxEmails: z.number(),
|
|
1002
948
|
}),
|
|
1003
|
-
returns: z
|
|
949
|
+
returns: z
|
|
950
|
+
.object({
|
|
1004
951
|
allowed: z.boolean(),
|
|
1005
952
|
count: z.number(),
|
|
1006
953
|
limit: z.number(),
|
|
1007
954
|
timeWindowMs: z.number(),
|
|
1008
955
|
retryAfter: z.number().optional(),
|
|
1009
|
-
})
|
|
956
|
+
})
|
|
957
|
+
.catchall(z.any()),
|
|
1010
958
|
handler: async (ctx, args) => {
|
|
1011
959
|
const cutoffTime = Date.now() - args.timeWindowMs;
|
|
1012
960
|
const operations = await ctx.db
|
|
@@ -1,3 +1,68 @@
|
|
|
1
|
-
declare const _default:
|
|
1
|
+
declare const _default: import("convex/server").SchemaDefinition<{
|
|
2
|
+
contacts: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
3
|
+
firstName?: string | undefined;
|
|
4
|
+
lastName?: string | undefined;
|
|
5
|
+
userId?: string | undefined;
|
|
6
|
+
source?: string | undefined;
|
|
7
|
+
subscribed?: boolean | undefined;
|
|
8
|
+
userGroup?: string | undefined;
|
|
9
|
+
loopsContactId?: string | undefined;
|
|
10
|
+
email: string;
|
|
11
|
+
createdAt: number;
|
|
12
|
+
updatedAt: number;
|
|
13
|
+
}, import("zodvex").ConvexValidatorFromZodFieldsAuto<{
|
|
14
|
+
email: import("zod").ZodString;
|
|
15
|
+
firstName: import("zod").ZodOptional<import("zod").ZodString>;
|
|
16
|
+
lastName: import("zod").ZodOptional<import("zod").ZodString>;
|
|
17
|
+
userId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
18
|
+
source: import("zod").ZodOptional<import("zod").ZodString>;
|
|
19
|
+
subscribed: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
20
|
+
userGroup: import("zod").ZodOptional<import("zod").ZodString>;
|
|
21
|
+
loopsContactId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
22
|
+
createdAt: import("zod").ZodNumber;
|
|
23
|
+
updatedAt: import("zod").ZodNumber;
|
|
24
|
+
}>, "required", "email" | "firstName" | "lastName" | "userId" | "source" | "subscribed" | "userGroup" | "loopsContactId" | "createdAt" | "updatedAt">, {
|
|
25
|
+
email: ["email", "_creationTime"];
|
|
26
|
+
userId: ["userId", "_creationTime"];
|
|
27
|
+
userGroup: ["userGroup", "_creationTime"];
|
|
28
|
+
source: ["source", "_creationTime"];
|
|
29
|
+
subscribed: ["subscribed", "_creationTime"];
|
|
30
|
+
}, {}, {}>;
|
|
31
|
+
emailOperations: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
32
|
+
actorId?: string | undefined;
|
|
33
|
+
transactionalId?: string | undefined;
|
|
34
|
+
campaignId?: string | undefined;
|
|
35
|
+
loopId?: string | undefined;
|
|
36
|
+
eventName?: string | undefined;
|
|
37
|
+
messageId?: string | undefined;
|
|
38
|
+
metadata?: Record<string, any> | undefined;
|
|
39
|
+
email: string;
|
|
40
|
+
success: boolean;
|
|
41
|
+
operationType: "transactional" | "event" | "campaign" | "loop";
|
|
42
|
+
timestamp: number;
|
|
43
|
+
}, import("zodvex").ConvexValidatorFromZodFieldsAuto<{
|
|
44
|
+
operationType: import("zod").ZodEnum<{
|
|
45
|
+
transactional: "transactional";
|
|
46
|
+
event: "event";
|
|
47
|
+
campaign: "campaign";
|
|
48
|
+
loop: "loop";
|
|
49
|
+
}>;
|
|
50
|
+
email: import("zod").ZodString;
|
|
51
|
+
actorId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
52
|
+
transactionalId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
53
|
+
campaignId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
54
|
+
loopId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
55
|
+
eventName: import("zod").ZodOptional<import("zod").ZodString>;
|
|
56
|
+
timestamp: import("zod").ZodNumber;
|
|
57
|
+
success: import("zod").ZodBoolean;
|
|
58
|
+
messageId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
59
|
+
metadata: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
|
|
60
|
+
}>, "required", "email" | "success" | "operationType" | "actorId" | "transactionalId" | "campaignId" | "loopId" | "eventName" | "timestamp" | "messageId" | "metadata" | `metadata.${string}`>, {
|
|
61
|
+
email: ["email", "timestamp", "_creationTime"];
|
|
62
|
+
actorId: ["actorId", "timestamp", "_creationTime"];
|
|
63
|
+
operationType: ["operationType", "timestamp", "_creationTime"];
|
|
64
|
+
timestamp: ["timestamp", "_creationTime"];
|
|
65
|
+
}, {}, {}>;
|
|
66
|
+
}, true>;
|
|
2
67
|
export default _default;
|
|
3
68
|
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/component/schema.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/component/schema.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,wBAYG"}
|