@devwithbobby/loops 0.1.19 → 0.2.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.
Files changed (37) hide show
  1. package/dist/client/index.d.ts +47 -12
  2. package/dist/client/index.d.ts.map +1 -1
  3. package/dist/client/index.js +42 -4
  4. package/dist/component/_generated/api.d.ts +185 -1
  5. package/dist/component/_generated/api.d.ts.map +1 -1
  6. package/dist/component/_generated/component.d.ts +12 -5
  7. package/dist/component/_generated/component.d.ts.map +1 -1
  8. package/dist/component/_generated/dataModel.d.ts +1 -1
  9. package/dist/component/aggregates.d.ts +42 -0
  10. package/dist/component/aggregates.d.ts.map +1 -0
  11. package/dist/component/aggregates.js +54 -0
  12. package/dist/component/convex.config.d.ts.map +1 -1
  13. package/dist/component/convex.config.js +2 -0
  14. package/dist/component/helpers.d.ts.map +1 -1
  15. package/dist/component/http.js +1 -1
  16. package/dist/component/lib.d.ts +66 -17
  17. package/dist/component/lib.d.ts.map +1 -1
  18. package/dist/component/lib.js +194 -73
  19. package/dist/component/schema.d.ts +2 -2
  20. package/dist/component/tables/contacts.d.ts.map +1 -1
  21. package/dist/component/tables/emailOperations.d.ts +4 -4
  22. package/dist/component/tables/emailOperations.d.ts.map +1 -1
  23. package/dist/test.d.ts +2 -2
  24. package/dist/types.d.ts +249 -62
  25. package/dist/types.d.ts.map +1 -1
  26. package/dist/types.js +4 -2
  27. package/dist/utils.d.ts +6 -6
  28. package/package.json +15 -9
  29. package/src/client/index.ts +52 -6
  30. package/src/component/_generated/api.ts +190 -1
  31. package/src/component/_generated/component.ts +10 -5
  32. package/src/component/_generated/dataModel.ts +1 -1
  33. package/src/component/aggregates.ts +89 -0
  34. package/src/component/convex.config.ts +3 -0
  35. package/src/component/http.ts +1 -1
  36. package/src/component/lib.ts +226 -89
  37. package/src/types.ts +20 -122
@@ -82,7 +82,7 @@ http.route({
82
82
  source: url.searchParams.get("source") ?? undefined,
83
83
  subscribed: booleanFromQuery(url.searchParams.get("subscribed")),
84
84
  limit: numberFromQuery(url.searchParams.get("limit"), 100),
85
- offset: numberFromQuery(url.searchParams.get("offset"), 0),
85
+ cursor: url.searchParams.get("cursor") ?? null,
86
86
  });
87
87
  return jsonResponse(data);
88
88
  }
@@ -36,9 +36,15 @@ export declare const logEmailOperation: import("convex/server").RegisteredMutati
36
36
  * Count contacts in the database
37
37
  * Can filter by audience criteria (userGroup, source, subscribed status)
38
38
  *
39
- * Note: When multiple filters are provided, only one index can be used.
40
- * Additional filters are applied in-memory, which is efficient for small result sets.
41
- * For large contact lists with multiple filters, consider using a composite index.
39
+ * For userGroup-only filtering, uses efficient O(log n) aggregate counting.
40
+ * For other filters (source, subscribed), uses indexed queries with a read limit.
41
+ *
42
+ * IMPORTANT: Before using this with existing data, run the backfillContactAggregate
43
+ * mutation to populate the aggregate with existing contacts.
44
+ *
45
+ * NOTE: When filtering by source or subscribed, counts are capped at MAX_COUNT_LIMIT
46
+ * to avoid query read limit errors. For exact counts with large datasets, use
47
+ * userGroup-only filtering which uses efficient aggregate counting.
42
48
  */
43
49
  export declare const countContacts: import("convex/server").RegisteredQuery<"public", {
44
50
  userGroup?: string | undefined;
@@ -46,19 +52,22 @@ export declare const countContacts: import("convex/server").RegisteredQuery<"pub
46
52
  subscribed?: boolean | undefined;
47
53
  }, Promise<number>>;
48
54
  /**
49
- * List contacts from the database with pagination
55
+ * List contacts from the database with cursor-based pagination
50
56
  * Can filter by audience criteria (userGroup, source, subscribed status)
51
57
  * Returns actual contact data, not just a count
52
58
  *
59
+ * Uses cursor-based pagination for efficient querying - only reads documents
60
+ * from the cursor position forward, not all preceding documents.
61
+ *
53
62
  * Note: When multiple filters are provided, only one index can be used.
54
- * Additional filters are applied in-memory before pagination.
63
+ * Additional filters are applied in-memory after fetching.
55
64
  */
56
65
  export declare const listContacts: import("convex/server").RegisteredQuery<"public", {
57
66
  limit: number;
58
- offset: number;
59
67
  userGroup?: string | undefined;
60
68
  source?: string | undefined;
61
69
  subscribed?: boolean | undefined;
70
+ cursor?: string | null | undefined;
62
71
  }, Promise<{
63
72
  contacts: {
64
73
  _id: string;
@@ -73,10 +82,8 @@ export declare const listContacts: import("convex/server").RegisteredQuery<"publ
73
82
  userGroup?: string | undefined;
74
83
  loopsContactId?: string | undefined;
75
84
  }[];
76
- total: number;
77
- limit: number;
78
- offset: number;
79
- hasMore: boolean;
85
+ continueCursor: string | null;
86
+ isDone: boolean;
80
87
  }>>;
81
88
  /**
82
89
  * Add or update a contact in Loops
@@ -242,7 +249,10 @@ export declare const resubscribeContact: import("convex/server").RegisteredActio
242
249
  }>>;
243
250
  /**
244
251
  * Check for spam patterns: too many emails to the same recipient in a time window
245
- * Returns email addresses that received too many emails
252
+ * Returns email addresses that received too many emails.
253
+ *
254
+ * NOTE: Analysis is limited to the most recent MAX_SPAM_DETECTION_LIMIT operations
255
+ * in the time window to avoid query read limit errors.
246
256
  */
247
257
  export declare const detectRecipientSpam: import("convex/server").RegisteredQuery<"public", {
248
258
  timeWindowMs: number;
@@ -255,7 +265,10 @@ export declare const detectRecipientSpam: import("convex/server").RegisteredQuer
255
265
  }[]>>;
256
266
  /**
257
267
  * Check for spam patterns: too many emails from the same actor/user
258
- * Returns actor IDs that sent too many emails
268
+ * Returns actor IDs that sent too many emails.
269
+ *
270
+ * NOTE: Analysis is limited to the most recent MAX_SPAM_DETECTION_LIMIT operations
271
+ * in the time window to avoid query read limit errors.
259
272
  */
260
273
  export declare const detectActorSpam: import("convex/server").RegisteredQuery<"public", {
261
274
  timeWindowMs: number;
@@ -266,7 +279,11 @@ export declare const detectActorSpam: import("convex/server").RegisteredQuery<"p
266
279
  timeWindowMs: number;
267
280
  }[]>>;
268
281
  /**
269
- * Get recent email operation statistics for monitoring
282
+ * Get recent email operation statistics for monitoring.
283
+ *
284
+ * NOTE: Statistics are calculated from the most recent MAX_SPAM_DETECTION_LIMIT
285
+ * operations in the time window to avoid query read limit errors. For high-volume
286
+ * applications, consider using scheduled jobs with pagination for exact statistics.
270
287
  */
271
288
  export declare const getEmailStats: import("convex/server").RegisteredQuery<"public", {
272
289
  timeWindowMs: number;
@@ -281,7 +298,10 @@ export declare const getEmailStats: import("convex/server").RegisteredQuery<"pub
281
298
  }>>;
282
299
  /**
283
300
  * Detect rapid-fire email sending patterns (multiple emails sent in quick succession)
284
- * Returns suspicious patterns indicating potential spam
301
+ * Returns suspicious patterns indicating potential spam.
302
+ *
303
+ * NOTE: Analysis is limited to the most recent MAX_SPAM_DETECTION_LIMIT operations
304
+ * in the time window to avoid query read limit errors.
285
305
  */
286
306
  export declare const detectRapidFirePatterns: import("convex/server").RegisteredQuery<"public", {
287
307
  timeWindowMs: number;
@@ -296,7 +316,10 @@ export declare const detectRapidFirePatterns: import("convex/server").Registered
296
316
  }[]>>;
297
317
  /**
298
318
  * Rate limiting: Check if an email can be sent to a recipient
299
- * Based on recent email operations in the database
319
+ * Based on recent email operations in the database.
320
+ *
321
+ * Uses efficient .take() query - only reads the minimum number of documents
322
+ * needed to determine if the rate limit is exceeded.
300
323
  */
301
324
  export declare const checkRecipientRateLimit: import("convex/server").RegisteredQuery<"public", {
302
325
  email: string;
@@ -312,7 +335,10 @@ export declare const checkRecipientRateLimit: import("convex/server").Registered
312
335
  }>>;
313
336
  /**
314
337
  * Rate limiting: Check if an actor/user can send more emails
315
- * Based on recent email operations in the database
338
+ * Based on recent email operations in the database.
339
+ *
340
+ * Uses efficient .take() query - only reads the minimum number of documents
341
+ * needed to determine if the rate limit is exceeded.
316
342
  */
317
343
  export declare const checkActorRateLimit: import("convex/server").RegisteredQuery<"public", {
318
344
  actorId: string;
@@ -327,7 +353,10 @@ export declare const checkActorRateLimit: import("convex/server").RegisteredQuer
327
353
  }>>;
328
354
  /**
329
355
  * Rate limiting: Check global email sending rate
330
- * Checks total email operations across all senders
356
+ * Checks total email operations across all senders.
357
+ *
358
+ * Uses efficient .take() query - only reads the minimum number of documents
359
+ * needed to determine if the rate limit is exceeded.
331
360
  */
332
361
  export declare const checkGlobalRateLimit: import("convex/server").RegisteredQuery<"public", {
333
362
  timeWindowMs: number;
@@ -338,4 +367,24 @@ export declare const checkGlobalRateLimit: import("convex/server").RegisteredQue
338
367
  limit: number;
339
368
  timeWindowMs: number;
340
369
  }>>;
370
+ /**
371
+ * Backfill the contact aggregate with existing contacts.
372
+ * Run this mutation after upgrading to a version with aggregate support.
373
+ *
374
+ * This processes contacts in batches to avoid timeout issues with large datasets.
375
+ * Call repeatedly with the returned cursor until isDone is true.
376
+ *
377
+ * Usage:
378
+ * 1. First call with clear: true to reset the aggregate
379
+ * 2. Subsequent calls with the returned cursor until isDone is true
380
+ */
381
+ export declare const backfillContactAggregate: import("convex/server").RegisteredMutation<"public", {
382
+ batchSize: number;
383
+ cursor?: string | null | undefined;
384
+ clear?: boolean | undefined;
385
+ }, Promise<{
386
+ processed: number;
387
+ cursor: string | null;
388
+ isDone: boolean;
389
+ }>>;
341
390
  //# sourceMappingURL=lib.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../../src/component/lib.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,eAAO,MAAM,YAAY;;;;;;;;;iBA6CvB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa;;iBAexB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;iBAkC5B,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;;;;mBA0DxB,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;GA8FvB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;GA+GrB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;GAgDxB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;GAiD5B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,SAAS;;;;;;;GAyCpB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa;;;;;GA0BxB,CAAC;AAEH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,WAAW;;;;;;;;;GA4DtB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;GA4DtB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;GA8D9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;;GA2B7B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;;GA2B7B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;KA8C9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,eAAe;;;;;;;KA4C1B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;GAkDxB,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;KAsGlC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;GA+ClC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;GA6C9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;GA8B/B,CAAC"}
1
+ {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../../src/component/lib.ts"],"names":[],"mappings":"AAkBA;;GAEG;AACH,eAAO,MAAM,YAAY;;;;;;;;;iBA2DvB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa;;iBAiBxB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;iBAkC5B,CAAC;AAUH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa;;;;mBA8DxB,CAAC;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;GAgGvB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;GA+GrB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;GAgDxB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;GAiD5B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,SAAS;;;;;;;GAyCpB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa;;;;;GA0BxB,CAAC;AAEH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,WAAW;;;;;;;;;GA4DtB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;GA4DtB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;GA8D9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;;GA2B7B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;;GA2B7B,CAAC;AASH;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;KA8C9B,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,eAAe;;;;;;;KA4C1B,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;GAkDxB,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;KAsGlC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;GAiDlC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;GA+C9B,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;GAiC/B,CAAC;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB;;;;;;;;GAsCnC,CAAC"}