@devwithbobby/loops 0.1.7 → 0.1.9

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.
@@ -56,7 +56,8 @@ export declare const triggerLoop: any;
56
56
  export declare const findContact: any;
57
57
  /**
58
58
  * Batch create contacts
59
- * Create multiple contacts in a single API call
59
+ * Creates multiple contacts sequentially using the single contact create endpoint.
60
+ * Note: Loops.so doesn't have a batch endpoint, so we create contacts one by one.
60
61
  */
61
62
  export declare const batchCreateContacts: any;
62
63
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../../src/component/lib.ts"],"names":[],"mappings":"AA0BA;;GAEG;AACH,eAAO,MAAM,YAAY,KA6CvB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,KAexB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB,KAgC5B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,aAAa,KAwCxB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,UAAU,KAiHrB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,KAoDxB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB,KAqD5B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,SAAS,KAgCpB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,KA8BxB,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,YAAY,KAkFvB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,WAAW,KA+CtB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,WAAW,KAoDtB,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,KA4C9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,KA+B7B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,KA+B7B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,KAwC9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,eAAe,KAwC1B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,KAgDxB,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,uBAAuB,KAsGlC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,uBAAuB,KA6ClC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,KA6C9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,oBAAoB,KA8B/B,CAAC"}
1
+ {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../../src/component/lib.ts"],"names":[],"mappings":"AA0BA;;GAEG;AACH,eAAO,MAAM,YAAY,KA6CvB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,KAexB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB,KAgC5B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,aAAa,KAwCxB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,UAAU,KAiHrB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,KAoDxB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB,KAqD5B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,SAAS,KAgCpB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,KA8BxB,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,YAAY,KAkFvB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,WAAW,KA+CtB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,WAAW,KA2DtB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,KAyD9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,KA+B7B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,KA+B7B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,KAwC9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,eAAe,KAwC1B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,KAgDxB,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,uBAAuB,KAsGlC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,uBAAuB,KA6ClC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,KA6C9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,oBAAoB,KA8B/B,CAAC"}
@@ -589,15 +589,15 @@ export const findContact = za({
589
589
  success: z.boolean(),
590
590
  contact: z
591
591
  .object({
592
- id: z.string().optional(),
593
- email: z.string().optional(),
594
- firstName: z.string().optional(),
595
- lastName: z.string().optional(),
596
- source: z.string().optional(),
597
- subscribed: z.boolean().optional(),
598
- userGroup: z.string().optional(),
599
- userId: z.string().optional(),
600
- createdAt: z.string().optional(),
592
+ id: z.string().nullable().optional(),
593
+ email: z.string().nullable().optional(),
594
+ firstName: z.string().nullable().optional(),
595
+ lastName: z.string().nullable().optional(),
596
+ source: z.string().nullable().optional(),
597
+ subscribed: z.boolean().nullable().optional(),
598
+ userGroup: z.string().nullable().optional(),
599
+ userId: z.string().nullable().optional(),
600
+ createdAt: z.string().nullable().optional(),
601
601
  })
602
602
  .optional(),
603
603
  }),
@@ -619,7 +619,11 @@ export const findContact = za({
619
619
  }
620
620
  const data = (await response.json());
621
621
  // Handle case where Loops returns an array instead of a single object
622
- const contact = Array.isArray(data) ? data[0] : data;
622
+ let contact = Array.isArray(data) ? data[0] : data;
623
+ // Convert null values to undefined for optional fields (Zod handles undefined but not null in optional())
624
+ if (contact) {
625
+ contact = Object.fromEntries(Object.entries(contact).map(([key, value]) => [key, value === null ? undefined : value]));
626
+ }
623
627
  return {
624
628
  success: true,
625
629
  contact: contact,
@@ -628,7 +632,8 @@ export const findContact = za({
628
632
  });
629
633
  /**
630
634
  * Batch create contacts
631
- * Create multiple contacts in a single API call
635
+ * Creates multiple contacts sequentially using the single contact create endpoint.
636
+ * Note: Loops.so doesn't have a batch endpoint, so we create contacts one by one.
632
637
  */
633
638
  export const batchCreateContacts = za({
634
639
  args: z.object({
@@ -638,36 +643,52 @@ export const batchCreateContacts = za({
638
643
  returns: z.object({
639
644
  success: z.boolean(),
640
645
  created: z.number().optional(),
646
+ failed: z.number().optional(),
647
+ results: z.array(z.object({
648
+ email: z.string(),
649
+ success: z.boolean(),
650
+ error: z.string().optional(),
651
+ })).optional(),
641
652
  }),
642
653
  handler: async (ctx, args) => {
643
- const response = await fetch(`${LOOPS_API_BASE_URL}/contacts/batch`, {
644
- method: "POST",
645
- headers: {
646
- Authorization: `Bearer ${args.apiKey}`,
647
- "Content-Type": "application/json",
648
- },
649
- body: JSON.stringify({ contacts: args.contacts }),
650
- });
651
- if (!response.ok) {
652
- const errorText = await response.text();
653
- console.error(`Loops API error [${response.status}]:`, errorText);
654
- throw sanitizeError(response.status, errorText);
655
- }
656
- const data = (await response.json());
654
+ let created = 0;
655
+ let failed = 0;
656
+ const results = [];
657
+ // Create contacts one by one since Loops.so doesn't have a batch endpoint
657
658
  for (const contact of args.contacts) {
658
- await ctx.runMutation((internal.lib.storeContact), {
659
- email: contact.email,
660
- firstName: contact.firstName,
661
- lastName: contact.lastName,
662
- userId: contact.userId,
663
- source: contact.source,
664
- subscribed: contact.subscribed,
665
- userGroup: contact.userGroup,
666
- });
659
+ try {
660
+ // Use the addContact function which handles create/update logic
661
+ const result = await ctx.runAction((internal.lib).addContact, {
662
+ apiKey: args.apiKey,
663
+ contact,
664
+ });
665
+ if (result.success) {
666
+ created++;
667
+ results.push({ email: contact.email, success: true });
668
+ }
669
+ else {
670
+ failed++;
671
+ results.push({
672
+ email: contact.email,
673
+ success: false,
674
+ error: "Unknown error"
675
+ });
676
+ }
677
+ }
678
+ catch (error) {
679
+ failed++;
680
+ results.push({
681
+ email: contact.email,
682
+ success: false,
683
+ error: error instanceof Error ? error.message : String(error),
684
+ });
685
+ }
667
686
  }
668
687
  return {
669
- success: true,
670
- created: data.created ?? args.contacts.length,
688
+ success: created > 0,
689
+ created,
690
+ failed,
691
+ results,
671
692
  };
672
693
  },
673
694
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devwithbobby/loops",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Convex component for integrating with Loops.so email marketing platform",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -640,15 +640,15 @@ export const findContact = za({
640
640
  success: z.boolean(),
641
641
  contact: z
642
642
  .object({
643
- id: z.string().optional(),
644
- email: z.string().optional(),
645
- firstName: z.string().optional(),
646
- lastName: z.string().optional(),
647
- source: z.string().optional(),
648
- subscribed: z.boolean().optional(),
649
- userGroup: z.string().optional(),
650
- userId: z.string().optional(),
651
- createdAt: z.string().optional(),
643
+ id: z.string().nullable().optional(),
644
+ email: z.string().nullable().optional(),
645
+ firstName: z.string().nullable().optional(),
646
+ lastName: z.string().nullable().optional(),
647
+ source: z.string().nullable().optional(),
648
+ subscribed: z.boolean().nullable().optional(),
649
+ userGroup: z.string().nullable().optional(),
650
+ userId: z.string().nullable().optional(),
651
+ createdAt: z.string().nullable().optional(),
652
652
  })
653
653
  .optional(),
654
654
  }),
@@ -676,7 +676,14 @@ export const findContact = za({
676
676
  const data = (await response.json()) as Record<string, any> | Array<Record<string, any>>;
677
677
 
678
678
  // Handle case where Loops returns an array instead of a single object
679
- const contact = Array.isArray(data) ? data[0] : data;
679
+ let contact = Array.isArray(data) ? data[0] : data;
680
+
681
+ // Convert null values to undefined for optional fields (Zod handles undefined but not null in optional())
682
+ if (contact) {
683
+ contact = Object.fromEntries(
684
+ Object.entries(contact).map(([key, value]) => [key, value === null ? undefined : value])
685
+ ) as Record<string, any>;
686
+ }
680
687
 
681
688
  return {
682
689
  success: true,
@@ -687,7 +694,8 @@ export const findContact = za({
687
694
 
688
695
  /**
689
696
  * Batch create contacts
690
- * Create multiple contacts in a single API call
697
+ * Creates multiple contacts sequentially using the single contact create endpoint.
698
+ * Note: Loops.so doesn't have a batch endpoint, so we create contacts one by one.
691
699
  */
692
700
  export const batchCreateContacts = za({
693
701
  args: z.object({
@@ -697,40 +705,53 @@ export const batchCreateContacts = za({
697
705
  returns: z.object({
698
706
  success: z.boolean(),
699
707
  created: z.number().optional(),
708
+ failed: z.number().optional(),
709
+ results: z.array(z.object({
710
+ email: z.string(),
711
+ success: z.boolean(),
712
+ error: z.string().optional(),
713
+ })).optional(),
700
714
  }),
701
715
  handler: async (ctx, args) => {
702
- const response = await fetch(`${LOOPS_API_BASE_URL}/contacts/batch`, {
703
- method: "POST",
704
- headers: {
705
- Authorization: `Bearer ${args.apiKey}`,
706
- "Content-Type": "application/json",
707
- },
708
- body: JSON.stringify({ contacts: args.contacts }),
709
- });
710
-
711
- if (!response.ok) {
712
- const errorText = await response.text();
713
- console.error(`Loops API error [${response.status}]:`, errorText);
714
- throw sanitizeError(response.status, errorText);
715
- }
716
-
717
- const data = (await response.json()) as { created?: number };
716
+ let created = 0;
717
+ let failed = 0;
718
+ const results: Array<{ email: string; success: boolean; error?: string }> = [];
718
719
 
720
+ // Create contacts one by one since Loops.so doesn't have a batch endpoint
719
721
  for (const contact of args.contacts) {
720
- await ctx.runMutation(((internal as any).lib.storeContact) as any, {
721
- email: contact.email,
722
- firstName: contact.firstName,
723
- lastName: contact.lastName,
724
- userId: contact.userId,
725
- source: contact.source,
726
- subscribed: contact.subscribed,
727
- userGroup: contact.userGroup,
728
- });
722
+ try {
723
+ // Use the addContact function which handles create/update logic
724
+ const result = await ctx.runAction(((internal as any).lib).addContact as any, {
725
+ apiKey: args.apiKey,
726
+ contact,
727
+ });
728
+
729
+ if (result.success) {
730
+ created++;
731
+ results.push({ email: contact.email, success: true });
732
+ } else {
733
+ failed++;
734
+ results.push({
735
+ email: contact.email,
736
+ success: false,
737
+ error: "Unknown error"
738
+ });
739
+ }
740
+ } catch (error) {
741
+ failed++;
742
+ results.push({
743
+ email: contact.email,
744
+ success: false,
745
+ error: error instanceof Error ? error.message : String(error),
746
+ });
747
+ }
729
748
  }
730
749
 
731
750
  return {
732
- success: true,
733
- created: data.created ?? args.contacts.length,
751
+ success: created > 0,
752
+ created,
753
+ failed,
754
+ results,
734
755
  };
735
756
  },
736
757
  });