@devwithbobby/loops 0.1.18 → 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 (52) hide show
  1. package/dist/client/index.d.ts +133 -103
  2. package/dist/client/index.d.ts.map +1 -1
  3. package/dist/client/index.js +55 -10
  4. package/dist/component/_generated/api.d.ts +228 -0
  5. package/dist/component/_generated/api.d.ts.map +1 -0
  6. package/{src → dist}/component/_generated/api.js +10 -3
  7. package/dist/component/_generated/component.d.ts +266 -0
  8. package/dist/component/_generated/component.d.ts.map +1 -0
  9. package/dist/component/_generated/component.js +9 -0
  10. package/dist/component/_generated/dataModel.d.ts +46 -0
  11. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  12. package/dist/component/_generated/dataModel.js +10 -0
  13. package/{src → dist}/component/_generated/server.d.ts +10 -38
  14. package/dist/component/_generated/server.d.ts.map +1 -0
  15. package/{src → dist}/component/_generated/server.js +9 -22
  16. package/dist/component/aggregates.d.ts +42 -0
  17. package/dist/component/aggregates.d.ts.map +1 -0
  18. package/dist/component/aggregates.js +54 -0
  19. package/dist/component/convex.config.d.ts.map +1 -1
  20. package/dist/component/convex.config.js +2 -22
  21. package/dist/component/helpers.d.ts +1 -1
  22. package/dist/component/helpers.d.ts.map +1 -1
  23. package/dist/component/helpers.js +1 -2
  24. package/dist/component/http.js +1 -1
  25. package/dist/component/lib.d.ts +66 -17
  26. package/dist/component/lib.d.ts.map +1 -1
  27. package/dist/component/lib.js +194 -73
  28. package/dist/component/schema.d.ts +2 -2
  29. package/dist/component/tables/contacts.d.ts.map +1 -1
  30. package/dist/component/tables/emailOperations.d.ts +4 -4
  31. package/dist/component/tables/emailOperations.d.ts.map +1 -1
  32. package/dist/test.d.ts +83 -0
  33. package/dist/test.d.ts.map +1 -0
  34. package/dist/test.js +16 -0
  35. package/dist/types.d.ts +249 -62
  36. package/dist/types.d.ts.map +1 -1
  37. package/dist/types.js +4 -2
  38. package/dist/utils.d.ts +6 -6
  39. package/package.json +25 -13
  40. package/src/client/index.ts +69 -18
  41. package/src/component/_generated/api.ts +249 -0
  42. package/src/component/_generated/component.ts +328 -0
  43. package/src/component/_generated/server.ts +161 -0
  44. package/src/component/aggregates.ts +89 -0
  45. package/src/component/convex.config.ts +2 -26
  46. package/src/component/helpers.ts +2 -2
  47. package/src/component/http.ts +1 -1
  48. package/src/component/lib.ts +226 -89
  49. package/src/test.ts +27 -0
  50. package/src/types.ts +20 -122
  51. package/src/component/_generated/api.d.ts +0 -47
  52. /package/src/component/_generated/{dataModel.d.ts → dataModel.ts} +0 -0
@@ -1,6 +1,6 @@
1
- import type { Mounts } from "../component/_generated/api";
2
- import type { RunActionCtx, RunQueryCtx, UseApi } from "../types";
3
- export type LoopsComponent = UseApi<Mounts>;
1
+ import type { ComponentApi } from "../component/_generated/component.js";
2
+ import type { RunActionCtx, RunMutationCtx, RunQueryCtx } from "../types";
3
+ export type LoopsComponent = ComponentApi;
4
4
  export interface ContactData {
5
5
  email: string;
6
6
  firstName?: string;
@@ -25,16 +25,21 @@ export declare class Loops {
25
25
  apiKey?: string;
26
26
  };
27
27
  private readonly lib;
28
+ private _apiKey?;
28
29
  constructor(component: LoopsComponent, options?: {
29
30
  apiKey?: string;
30
31
  });
31
- private readonly apiKey;
32
+ /**
33
+ * Get the API key, checking environment at call time (not constructor time).
34
+ * This allows the Loops client to be instantiated at module load time.
35
+ */
36
+ private get apiKey();
32
37
  /**
33
38
  * Add or update a contact in Loops
34
39
  */
35
40
  addContact(ctx: RunActionCtx, contact: ContactData): Promise<{
41
+ id?: string;
36
42
  success: boolean;
37
- id?: string | undefined;
38
43
  }>;
39
44
  /**
40
45
  * Update an existing contact in Loops
@@ -48,8 +53,8 @@ export declare class Loops {
48
53
  * Send a transactional email using a transactional ID
49
54
  */
50
55
  sendTransactional(ctx: RunActionCtx, options: TransactionalEmailOptions): Promise<{
56
+ messageId?: string;
51
57
  success: boolean;
52
- messageId?: string | undefined;
53
58
  }>;
54
59
  /**
55
60
  * Send an event to Loops to trigger email workflows
@@ -62,32 +67,32 @@ export declare class Loops {
62
67
  * Retrieves contact information from Loops
63
68
  */
64
69
  findContact(ctx: RunActionCtx, email: string): Promise<{
65
- success: boolean;
66
70
  contact?: {
67
- id?: string | null | undefined;
68
- email?: string | null | undefined;
69
- firstName?: string | null | undefined;
70
- lastName?: string | null | undefined;
71
- source?: string | null | undefined;
72
- subscribed?: boolean | null | undefined;
73
- userGroup?: string | null | undefined;
74
- userId?: string | null | undefined;
75
- createdAt?: string | null | undefined;
76
- } | undefined;
71
+ createdAt?: string | null;
72
+ email?: string | null;
73
+ firstName?: string | null;
74
+ id?: string | null;
75
+ lastName?: string | null;
76
+ source?: string | null;
77
+ subscribed?: boolean | null;
78
+ userGroup?: string | null;
79
+ userId?: string | null;
80
+ };
81
+ success: boolean;
77
82
  }>;
78
83
  /**
79
84
  * Batch create contacts
80
85
  * Create multiple contacts in a single API call
81
86
  */
82
87
  batchCreateContacts(ctx: RunActionCtx, contacts: ContactData[]): Promise<{
83
- success: boolean;
84
- created?: number | undefined;
85
- failed?: number | undefined;
86
- results?: {
88
+ created?: number;
89
+ failed?: number;
90
+ results?: Array<{
87
91
  email: string;
92
+ error?: string;
88
93
  success: boolean;
89
- error?: string | undefined;
90
- }[] | undefined;
94
+ }>;
95
+ success: boolean;
91
96
  }>;
92
97
  /**
93
98
  * Unsubscribe a contact
@@ -114,34 +119,35 @@ export declare class Loops {
114
119
  subscribed?: boolean;
115
120
  }): Promise<number>;
116
121
  /**
117
- * List contacts with pagination and optional filters
122
+ * List contacts with cursor-based pagination and optional filters
118
123
  * Returns actual contact data, not just a count
119
124
  * This queries the component's local database, not Loops API
125
+ *
126
+ * Uses cursor-based pagination for efficiency. Pass the `continueCursor`
127
+ * from the previous response as `cursor` to get the next page.
120
128
  */
121
129
  listContacts(ctx: RunQueryCtx, options?: {
122
130
  userGroup?: string;
123
131
  source?: string;
124
132
  subscribed?: boolean;
125
133
  limit?: number;
126
- offset?: number;
134
+ cursor?: string | null;
127
135
  }): Promise<{
128
- contacts: {
136
+ contacts: Array<{
129
137
  _id: string;
138
+ createdAt: number;
130
139
  email: string;
140
+ firstName?: string;
141
+ lastName?: string;
142
+ loopsContactId?: string;
143
+ source?: string;
131
144
  subscribed: boolean;
132
- createdAt: number;
133
145
  updatedAt: number;
134
- firstName?: string | undefined;
135
- lastName?: string | undefined;
136
- userId?: string | undefined;
137
- source?: string | undefined;
138
- userGroup?: string | undefined;
139
- loopsContactId?: string | undefined;
140
- }[];
141
- total: number;
142
- limit: number;
143
- offset: number;
144
- hasMore: boolean;
146
+ userGroup?: string;
147
+ userId?: string;
148
+ }>;
149
+ continueCursor: string | null;
150
+ isDone: boolean;
145
151
  }>;
146
152
  /**
147
153
  * Detect spam patterns: emails sent to the same recipient too frequently
@@ -150,9 +156,8 @@ export declare class Loops {
150
156
  timeWindowMs?: number;
151
157
  maxEmailsPerRecipient?: number;
152
158
  }): Promise<{
153
- [x: string]: any;
154
- email: string;
155
159
  count: number;
160
+ email: string;
156
161
  timeWindowMs: number;
157
162
  }[]>;
158
163
  /**
@@ -172,15 +177,12 @@ export declare class Loops {
172
177
  getEmailStats(ctx: RunQueryCtx, options?: {
173
178
  timeWindowMs?: number;
174
179
  }): Promise<{
175
- [x: string]: any;
176
- totalOperations: number;
177
- successfulOperations: number;
178
180
  failedOperations: number;
179
- operationsByType: {
180
- [x: string]: number;
181
- };
182
- uniqueRecipients: number;
181
+ operationsByType: Record<string, number>;
182
+ successfulOperations: number;
183
+ totalOperations: number;
183
184
  uniqueActors: number;
185
+ uniqueRecipients: number;
184
186
  }>;
185
187
  /**
186
188
  * Detect rapid-fire email sending patterns
@@ -189,12 +191,12 @@ export declare class Loops {
189
191
  timeWindowMs?: number;
190
192
  minEmailsInWindow?: number;
191
193
  }): Promise<{
194
+ actorId?: string;
192
195
  count: number;
193
- timeWindowMs: number;
196
+ email?: string;
194
197
  firstTimestamp: number;
195
198
  lastTimestamp: number;
196
- email?: string | undefined;
197
- actorId?: string | undefined;
199
+ timeWindowMs: number;
198
200
  }[]>;
199
201
  /**
200
202
  * Check if an email can be sent to a recipient based on rate limits
@@ -204,12 +206,11 @@ export declare class Loops {
204
206
  timeWindowMs: number;
205
207
  maxEmails: number;
206
208
  }): Promise<{
207
- [x: string]: any;
208
209
  allowed: boolean;
209
210
  count: number;
210
211
  limit: number;
212
+ retryAfter?: number;
211
213
  timeWindowMs: number;
212
- retryAfter?: number | undefined;
213
214
  }>;
214
215
  /**
215
216
  * Check if an actor/user can send more emails based on rate limits
@@ -222,8 +223,8 @@ export declare class Loops {
222
223
  allowed: boolean;
223
224
  count: number;
224
225
  limit: number;
226
+ retryAfter?: number;
225
227
  timeWindowMs: number;
226
- retryAfter?: number | undefined;
227
228
  }>;
228
229
  /**
229
230
  * Check global email sending rate limit
@@ -260,7 +261,34 @@ export declare class Loops {
260
261
  eventName?: string;
261
262
  }): Promise<{
262
263
  success: boolean;
263
- warning?: string | undefined;
264
+ warning?: string;
265
+ }>;
266
+ /**
267
+ * Backfill the contact aggregate with existing contacts.
268
+ * Run this after upgrading to a version with aggregate support.
269
+ *
270
+ * This processes contacts in batches to avoid timeout issues with large datasets.
271
+ * Call repeatedly with the returned cursor until isDone is true.
272
+ *
273
+ * Usage:
274
+ * ```ts
275
+ * // First call - clear existing aggregate and start backfill
276
+ * let result = await loops.backfillContactAggregate(ctx, { clear: true });
277
+ *
278
+ * // Continue until done
279
+ * while (!result.isDone) {
280
+ * result = await loops.backfillContactAggregate(ctx, { cursor: result.cursor });
281
+ * }
282
+ * ```
283
+ */
284
+ backfillContactAggregate(ctx: RunMutationCtx, options?: {
285
+ cursor?: string | null;
286
+ batchSize?: number;
287
+ clear?: boolean;
288
+ }): Promise<{
289
+ cursor: string | null;
290
+ isDone: boolean;
291
+ processed: number;
264
292
  }>;
265
293
  /**
266
294
  * For easy re-exporting.
@@ -279,8 +307,8 @@ export declare class Loops {
279
307
  userGroup?: string | undefined;
280
308
  email: string;
281
309
  }, Promise<{
310
+ id?: string;
282
311
  success: boolean;
283
- id?: string | undefined;
284
312
  }>>;
285
313
  updateContact: import("convex/server").RegisteredAction<"public", {
286
314
  firstName?: string | undefined;
@@ -299,8 +327,8 @@ export declare class Loops {
299
327
  email: string;
300
328
  transactionalId: string;
301
329
  }, Promise<{
330
+ messageId?: string;
302
331
  success: boolean;
303
- messageId?: string | undefined;
304
332
  }>>;
305
333
  sendEvent: import("convex/server").RegisteredAction<"public", {
306
334
  eventProperties?: any;
@@ -320,23 +348,23 @@ export declare class Loops {
320
348
  loopId: string;
321
349
  }, Promise<{
322
350
  success: boolean;
323
- warning?: string | undefined;
351
+ warning?: string;
324
352
  }>>;
325
353
  findContact: import("convex/server").RegisteredAction<"public", {
326
354
  email: string;
327
355
  }, Promise<{
328
- success: boolean;
329
356
  contact?: {
330
- id?: string | null | undefined;
331
- email?: string | null | undefined;
332
- firstName?: string | null | undefined;
333
- lastName?: string | null | undefined;
334
- source?: string | null | undefined;
335
- subscribed?: boolean | null | undefined;
336
- userGroup?: string | null | undefined;
337
- userId?: string | null | undefined;
338
- createdAt?: string | null | undefined;
339
- } | undefined;
357
+ createdAt?: string | null;
358
+ email?: string | null;
359
+ firstName?: string | null;
360
+ id?: string | null;
361
+ lastName?: string | null;
362
+ source?: string | null;
363
+ subscribed?: boolean | null;
364
+ userGroup?: string | null;
365
+ userId?: string | null;
366
+ };
367
+ success: boolean;
340
368
  }>>;
341
369
  batchCreateContacts: import("convex/server").RegisteredAction<"public", {
342
370
  contacts: {
@@ -349,14 +377,14 @@ export declare class Loops {
349
377
  email: string;
350
378
  }[];
351
379
  }, Promise<{
352
- success: boolean;
353
- created?: number | undefined;
354
- failed?: number | undefined;
355
- results?: {
380
+ created?: number;
381
+ failed?: number;
382
+ results?: Array<{
356
383
  email: string;
384
+ error?: string;
357
385
  success: boolean;
358
- error?: string | undefined;
359
- }[] | undefined;
386
+ }>;
387
+ success: boolean;
360
388
  }>>;
361
389
  unsubscribeContact: import("convex/server").RegisteredAction<"public", {
362
390
  email: string;
@@ -378,33 +406,30 @@ export declare class Loops {
378
406
  subscribed?: boolean | undefined;
379
407
  userGroup?: string | undefined;
380
408
  limit?: number | undefined;
381
- offset?: number | undefined;
409
+ cursor?: string | null | undefined;
382
410
  }, Promise<{
383
- contacts: {
411
+ contacts: Array<{
384
412
  _id: string;
413
+ createdAt: number;
385
414
  email: string;
415
+ firstName?: string;
416
+ lastName?: string;
417
+ loopsContactId?: string;
418
+ source?: string;
386
419
  subscribed: boolean;
387
- createdAt: number;
388
420
  updatedAt: number;
389
- firstName?: string | undefined;
390
- lastName?: string | undefined;
391
- userId?: string | undefined;
392
- source?: string | undefined;
393
- userGroup?: string | undefined;
394
- loopsContactId?: string | undefined;
395
- }[];
396
- total: number;
397
- limit: number;
398
- offset: number;
399
- hasMore: boolean;
421
+ userGroup?: string;
422
+ userId?: string;
423
+ }>;
424
+ continueCursor: string | null;
425
+ isDone: boolean;
400
426
  }>>;
401
427
  detectRecipientSpam: import("convex/server").RegisteredQuery<"public", {
402
428
  timeWindowMs?: number | undefined;
403
429
  maxEmailsPerRecipient?: number | undefined;
404
430
  }, Promise<{
405
- [x: string]: any;
406
- email: string;
407
431
  count: number;
432
+ email: string;
408
433
  timeWindowMs: number;
409
434
  }[]>>;
410
435
  detectActorSpam: import("convex/server").RegisteredQuery<"public", {
@@ -418,38 +443,34 @@ export declare class Loops {
418
443
  getEmailStats: import("convex/server").RegisteredQuery<"public", {
419
444
  timeWindowMs?: number | undefined;
420
445
  }, Promise<{
421
- [x: string]: any;
422
- totalOperations: number;
423
- successfulOperations: number;
424
446
  failedOperations: number;
425
- operationsByType: {
426
- [x: string]: number;
427
- };
428
- uniqueRecipients: number;
447
+ operationsByType: Record<string, number>;
448
+ successfulOperations: number;
449
+ totalOperations: number;
429
450
  uniqueActors: number;
451
+ uniqueRecipients: number;
430
452
  }>>;
431
453
  detectRapidFirePatterns: import("convex/server").RegisteredQuery<"public", {
432
454
  timeWindowMs?: number | undefined;
433
455
  minEmailsInWindow?: number | undefined;
434
456
  }, Promise<{
457
+ actorId?: string;
435
458
  count: number;
436
- timeWindowMs: number;
459
+ email?: string;
437
460
  firstTimestamp: number;
438
461
  lastTimestamp: number;
439
- email?: string | undefined;
440
- actorId?: string | undefined;
462
+ timeWindowMs: number;
441
463
  }[]>>;
442
464
  checkRecipientRateLimit: import("convex/server").RegisteredQuery<"public", {
443
465
  email: string;
444
466
  timeWindowMs: number;
445
467
  maxEmails: number;
446
468
  }, Promise<{
447
- [x: string]: any;
448
469
  allowed: boolean;
449
470
  count: number;
450
471
  limit: number;
472
+ retryAfter?: number;
451
473
  timeWindowMs: number;
452
- retryAfter?: number | undefined;
453
474
  }>>;
454
475
  checkActorRateLimit: import("convex/server").RegisteredQuery<"public", {
455
476
  actorId: string;
@@ -459,8 +480,8 @@ export declare class Loops {
459
480
  allowed: boolean;
460
481
  count: number;
461
482
  limit: number;
483
+ retryAfter?: number;
462
484
  timeWindowMs: number;
463
- retryAfter?: number | undefined;
464
485
  }>>;
465
486
  checkGlobalRateLimit: import("convex/server").RegisteredQuery<"public", {
466
487
  timeWindowMs: number;
@@ -471,6 +492,15 @@ export declare class Loops {
471
492
  limit: number;
472
493
  timeWindowMs: number;
473
494
  }>>;
495
+ backfillContactAggregate: import("convex/server").RegisteredMutation<"public", {
496
+ cursor?: string | null | undefined;
497
+ batchSize?: number | undefined;
498
+ clear?: boolean | undefined;
499
+ }, Promise<{
500
+ cursor: string | null;
501
+ isDone: boolean;
502
+ processed: number;
503
+ }>>;
474
504
  };
475
505
  }
476
506
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElE,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;AAE5C,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,yBAAyB;IACzC,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC1C;AAED,qBAAa,KAAK;IACjB,SAAgB,OAAO,CAAC,EAAE;QACzB,MAAM,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqC;gBAGxD,SAAS,EAAE,cAAc,EACzB,OAAO,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,CAAC;KAChB;IAwCF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC;;OAEG;IACG,UAAU,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW;;;;IAOxD;;OAEG;IACG,aAAa,CAClB,GAAG,EAAE,YAAY,EACjB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG;QAC/B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACxC;;;IASF;;OAEG;IACG,iBAAiB,CACtB,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,yBAAyB;;;;IAQnC;;OAEG;IACG,SAAS,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY;;;IAOxD;;;OAGG;IACG,WAAW,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM;;;;;;;;;;;;;;IAOlD;;;OAGG;IACG,mBAAmB,CAAC,GAAG,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE;;;;;;;;;;IAOpE;;;OAGG;IACG,kBAAkB,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM;;;IAOzD;;;OAGG;IACG,kBAAkB,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM;;;IAOzD;;;;OAIG;IACG,aAAa,CAClB,GAAG,EAAE,WAAW,EAChB,OAAO,CAAC,EAAE;QACT,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,OAAO,CAAC;KACrB;IAKF;;;;OAIG;IACG,YAAY,CACjB,GAAG,EAAE,WAAW,EAChB,OAAO,CAAC,EAAE;QACT,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KAChB;;;;;;;;;;;;;;;;;;;IAWF;;OAEG;IACG,mBAAmB,CACxB,GAAG,EAAE,WAAW,EAChB,OAAO,CAAC,EAAE;QACT,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,qBAAqB,CAAC,EAAE,MAAM,CAAC;KAC/B;;;;;;IAQF;;OAEG;IACG,eAAe,CACpB,GAAG,EAAE,WAAW,EAChB,OAAO,CAAC,EAAE;QACT,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC3B;;;;;IAQF;;OAEG;IACG,aAAa,CAClB,GAAG,EAAE,WAAW,EAChB,OAAO,CAAC,EAAE;QACT,YAAY,CAAC,EAAE,MAAM,CAAC;KACtB;;;;;;;;;;;IAOF;;OAEG;IACG,uBAAuB,CAC5B,GAAG,EAAE,WAAW,EAChB,OAAO,CAAC,EAAE;QACT,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC3B;;;;;;;;IAQF;;OAEG;IACG,uBAAuB,CAC5B,GAAG,EAAE,WAAW,EAChB,OAAO,EAAE;QACR,KAAK,EAAE,MAAM,CAAC;QACd,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;KAClB;;;;;;;;IAKF;;OAEG;IACG,mBAAmB,CACxB,GAAG,EAAE,WAAW,EAChB,OAAO,EAAE;QACR,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;KAClB;;;;;;;IAKF;;OAEG;IACG,oBAAoB,CACzB,GAAG,EAAE,WAAW,EAChB,OAAO,EAAE;QACR,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;KAClB;;;;;;IAKF;;OAEG;IACG,aAAa,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM;;;IAOpD;;;;;;;;;OASG;IACG,WAAW,CAChB,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE;QACR,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACxC,SAAS,CAAC,EAAE,MAAM,CAAC;KACnB;;;;IAQF;;;;;;OAMG;IACH,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwMH"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,9 @@
1
- import { actionGeneric, queryGeneric } from "convex/server";
1
+ import { actionGeneric, mutationGeneric, queryGeneric } from "convex/server";
2
2
  import { v } from "convex/values";
3
3
  export class Loops {
4
4
  options;
5
5
  lib;
6
+ _apiKey;
6
7
  constructor(component, options) {
7
8
  if (!component) {
8
9
  throw new Error("Loops component reference is required. " +
@@ -17,18 +18,24 @@ export class Loops {
17
18
  }
18
19
  this.lib = component.lib;
19
20
  this.options = options;
20
- const apiKey = options?.apiKey ?? process.env.LOOPS_API_KEY;
21
- if (!apiKey) {
22
- throw new Error("Loops API key is required. Set LOOPS_API_KEY in your Convex environment variables.");
23
- }
21
+ this._apiKey = options?.apiKey;
24
22
  if (options?.apiKey) {
25
23
  console.warn("API key passed directly via options. " +
26
24
  "For security, use LOOPS_API_KEY environment variable instead. " +
27
25
  "See README.md for details.");
28
26
  }
29
- this.apiKey = apiKey;
30
27
  }
31
- apiKey;
28
+ /**
29
+ * Get the API key, checking environment at call time (not constructor time).
30
+ * This allows the Loops client to be instantiated at module load time.
31
+ */
32
+ get apiKey() {
33
+ const key = this._apiKey ?? process.env.LOOPS_API_KEY;
34
+ if (!key) {
35
+ throw new Error("Loops API key is required. Set LOOPS_API_KEY in your Convex environment variables.");
36
+ }
37
+ return key;
38
+ }
32
39
  /**
33
40
  * Add or update a contact in Loops
34
41
  */
@@ -115,9 +122,12 @@ export class Loops {
115
122
  return ctx.runQuery(this.lib.countContacts, options ?? {});
116
123
  }
117
124
  /**
118
- * List contacts with pagination and optional filters
125
+ * List contacts with cursor-based pagination and optional filters
119
126
  * Returns actual contact data, not just a count
120
127
  * This queries the component's local database, not Loops API
128
+ *
129
+ * Uses cursor-based pagination for efficiency. Pass the `continueCursor`
130
+ * from the previous response as `cursor` to get the next page.
121
131
  */
122
132
  async listContacts(ctx, options) {
123
133
  return ctx.runQuery(this.lib.listContacts, {
@@ -125,7 +135,7 @@ export class Loops {
125
135
  source: options?.source,
126
136
  subscribed: options?.subscribed,
127
137
  limit: options?.limit ?? 100,
128
- offset: options?.offset ?? 0,
138
+ cursor: options?.cursor ?? null,
129
139
  });
130
140
  }
131
141
  /**
@@ -206,6 +216,31 @@ export class Loops {
206
216
  ...options,
207
217
  });
208
218
  }
219
+ /**
220
+ * Backfill the contact aggregate with existing contacts.
221
+ * Run this after upgrading to a version with aggregate support.
222
+ *
223
+ * This processes contacts in batches to avoid timeout issues with large datasets.
224
+ * Call repeatedly with the returned cursor until isDone is true.
225
+ *
226
+ * Usage:
227
+ * ```ts
228
+ * // First call - clear existing aggregate and start backfill
229
+ * let result = await loops.backfillContactAggregate(ctx, { clear: true });
230
+ *
231
+ * // Continue until done
232
+ * while (!result.isDone) {
233
+ * result = await loops.backfillContactAggregate(ctx, { cursor: result.cursor });
234
+ * }
235
+ * ```
236
+ */
237
+ async backfillContactAggregate(ctx, options) {
238
+ return ctx.runMutation(this.lib.backfillContactAggregate, {
239
+ cursor: options?.cursor ?? null,
240
+ batchSize: options?.batchSize ?? 100,
241
+ clear: options?.clear,
242
+ });
243
+ }
209
244
  /**
210
245
  * For easy re-exporting.
211
246
  * Apps can do
@@ -339,7 +374,7 @@ export class Loops {
339
374
  source: v.optional(v.string()),
340
375
  subscribed: v.optional(v.boolean()),
341
376
  limit: v.optional(v.number()),
342
- offset: v.optional(v.number()),
377
+ cursor: v.optional(v.union(v.string(), v.null())),
343
378
  },
344
379
  handler: async (ctx, args) => {
345
380
  return await this.listContacts(ctx, args);
@@ -409,6 +444,16 @@ export class Loops {
409
444
  return await this.checkGlobalRateLimit(ctx, args);
410
445
  },
411
446
  }),
447
+ backfillContactAggregate: mutationGeneric({
448
+ args: {
449
+ cursor: v.optional(v.union(v.string(), v.null())),
450
+ batchSize: v.optional(v.number()),
451
+ clear: v.optional(v.boolean()),
452
+ },
453
+ handler: async (ctx, args) => {
454
+ return await this.backfillContactAggregate(ctx, args);
455
+ },
456
+ }),
412
457
  };
413
458
  }
414
459
  }