@devwithbobby/loops 0.1.11 → 0.1.13

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.
@@ -32,7 +32,7 @@ export class Loops {
32
32
  public readonly options?: {
33
33
  apiKey?: string;
34
34
  };
35
-
35
+
36
36
  constructor(
37
37
  component: LoopsComponent,
38
38
  options?: {
@@ -43,26 +43,26 @@ export class Loops {
43
43
  throw new Error(
44
44
  "Loops component reference is required. " +
45
45
  "Make sure the component is mounted in your convex.config.ts and use: " +
46
- "new Loops(components.loops)"
46
+ "new Loops(components.loops)",
47
47
  );
48
48
  }
49
-
49
+
50
50
  if (!component.lib) {
51
51
  throw new Error(
52
52
  "Invalid component reference. " +
53
53
  "The component may not be properly mounted. " +
54
54
  "Ensure the component is correctly mounted in convex.config.ts: " +
55
- "app.use(loops);"
55
+ "app.use(loops);",
56
56
  );
57
57
  }
58
-
58
+
59
59
  this.component = component;
60
60
  this.options = options;
61
-
61
+
62
62
  const apiKey = options?.apiKey ?? process.env.LOOPS_API_KEY;
63
63
  if (!apiKey) {
64
64
  throw new Error(
65
- "Loops API key is required. Set LOOPS_API_KEY in your Convex environment variables."
65
+ "Loops API key is required. Set LOOPS_API_KEY in your Convex environment variables.",
66
66
  );
67
67
  }
68
68
 
@@ -87,7 +87,7 @@ export class Loops {
87
87
  throw new Error(
88
88
  "Loops component is not initialized. " +
89
89
  "Make sure to pass components.loops to the Loops constructor: " +
90
- "new Loops(components.loops)"
90
+ "new Loops(components.loops)",
91
91
  );
92
92
  }
93
93
  if (!this.component.lib) {
@@ -95,7 +95,7 @@ export class Loops {
95
95
  "Invalid component reference. " +
96
96
  "The component may not be properly mounted. " +
97
97
  "Ensure the component is correctly mounted in convex.config.ts: " +
98
- "app.use(loops);"
98
+ "app.use(loops);",
99
99
  );
100
100
  }
101
101
  return ctx.runAction((this.component.lib as any).addContact, {
@@ -124,7 +124,10 @@ export class Loops {
124
124
  /**
125
125
  * Send a transactional email using a transactional ID
126
126
  */
127
- async sendTransactional(ctx: RunActionCtx, options: TransactionalEmailOptions) {
127
+ async sendTransactional(
128
+ ctx: RunActionCtx,
129
+ options: TransactionalEmailOptions,
130
+ ) {
128
131
  return ctx.runAction((this.component.lib as any).sendTransactional, {
129
132
  apiKey: this.apiKey,
130
133
  ...options,
@@ -198,7 +201,34 @@ export class Loops {
198
201
  subscribed?: boolean;
199
202
  },
200
203
  ) {
201
- return ctx.runQuery((this.component.lib as any).countContacts, options ?? {});
204
+ return ctx.runQuery(
205
+ (this.component.lib as any).countContacts,
206
+ options ?? {},
207
+ );
208
+ }
209
+
210
+ /**
211
+ * List contacts with pagination and optional filters
212
+ * Returns actual contact data, not just a count
213
+ * This queries the component's local database, not Loops API
214
+ */
215
+ async listContacts(
216
+ ctx: RunQueryCtx,
217
+ options?: {
218
+ userGroup?: string;
219
+ source?: string;
220
+ subscribed?: boolean;
221
+ limit?: number;
222
+ offset?: number;
223
+ },
224
+ ) {
225
+ return ctx.runQuery((this.component.lib as any).listContacts, {
226
+ userGroup: options?.userGroup,
227
+ source: options?.source,
228
+ subscribed: options?.subscribed,
229
+ limit: options?.limit ?? 100,
230
+ offset: options?.offset ?? 0,
231
+ });
202
232
  }
203
233
 
204
234
  /**
@@ -274,7 +304,10 @@ export class Loops {
274
304
  maxEmails: number;
275
305
  },
276
306
  ) {
277
- return ctx.runQuery((this.component.lib as any).checkRecipientRateLimit, options);
307
+ return ctx.runQuery(
308
+ (this.component.lib as any).checkRecipientRateLimit,
309
+ options,
310
+ );
278
311
  }
279
312
 
280
313
  /**
@@ -288,7 +321,10 @@ export class Loops {
288
321
  maxEmails: number;
289
322
  },
290
323
  ) {
291
- return ctx.runQuery((this.component.lib as any).checkActorRateLimit, options);
324
+ return ctx.runQuery(
325
+ (this.component.lib as any).checkActorRateLimit,
326
+ options,
327
+ );
292
328
  }
293
329
 
294
330
  /**
@@ -301,7 +337,10 @@ export class Loops {
301
337
  maxEmails: number;
302
338
  },
303
339
  ) {
304
- return ctx.runQuery((this.component.lib as any).checkGlobalRateLimit, options);
340
+ return ctx.runQuery(
341
+ (this.component.lib as any).checkGlobalRateLimit,
342
+ options,
343
+ );
305
344
  }
306
345
 
307
346
  /**
@@ -314,37 +353,14 @@ export class Loops {
314
353
  });
315
354
  }
316
355
 
317
- /**
318
- * Send a campaign to contacts
319
- * Campaigns are one-time email sends to a segment or list of contacts
320
- */
321
- async sendCampaign(
322
- ctx: RunActionCtx,
323
- options: {
324
- campaignId: string;
325
- emails?: string[];
326
- transactionalId?: string;
327
- dataVariables?: Record<string, any>;
328
- audienceFilters?: {
329
- userGroup?: string;
330
- source?: string;
331
- };
332
- },
333
- ) {
334
- return ctx.runAction((this.component.lib as any).sendCampaign, {
335
- apiKey: this.apiKey,
336
- ...options,
337
- });
338
- }
339
-
340
356
  /**
341
357
  * Trigger a loop for a contact
342
358
  * Loops are automated email sequences that can be triggered by events
343
- *
359
+ *
344
360
  * Note: Loops.so doesn't have a direct loop trigger endpoint.
345
361
  * Loops are triggered through events. Make sure your loop is configured
346
362
  * in the Loops dashboard to listen for events.
347
- *
363
+ *
348
364
  * @param options.eventName - Optional event name. If not provided, uses `loop_{loopId}`
349
365
  */
350
366
  async triggerLoop(
@@ -366,7 +382,7 @@ export class Loops {
366
382
  * For easy re-exporting.
367
383
  * Apps can do
368
384
  * ```ts
369
- * export const { addContact, sendTransactional, sendEvent, sendCampaign, triggerLoop } = loops.api();
385
+ * export const { addContact, sendTransactional, sendEvent, triggerLoop } = loops.api();
370
386
  * ```
371
387
  */
372
388
  api() {
@@ -429,23 +445,6 @@ export class Loops {
429
445
  return await this.deleteContact(ctx, args.email);
430
446
  },
431
447
  }),
432
- sendCampaign: actionGeneric({
433
- args: {
434
- campaignId: v.string(),
435
- emails: v.optional(v.array(v.string())),
436
- transactionalId: v.optional(v.string()),
437
- dataVariables: v.optional(v.any()),
438
- audienceFilters: v.optional(
439
- v.object({
440
- userGroup: v.optional(v.string()),
441
- source: v.optional(v.string()),
442
- }),
443
- ),
444
- },
445
- handler: async (ctx, args) => {
446
- return await this.sendCampaign(ctx, args);
447
- },
448
- }),
449
448
  triggerLoop: actionGeneric({
450
449
  args: {
451
450
  loopId: v.string(),
@@ -13,7 +13,6 @@ const component = defineComponent("loops");
13
13
  listContacts: api.lib.listContacts,
14
14
  sendTransactional: api.lib.sendTransactional,
15
15
  sendEvent: api.lib.sendEvent,
16
- sendCampaign: api.lib.sendCampaign,
17
16
  triggerLoop: api.lib.triggerLoop,
18
17
  deleteContact: api.lib.deleteContact,
19
18
  detectRecipientSpam: api.lib.detectRecipientSpam,