@devwithbobby/loops 0.1.17 → 0.1.19
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/README.md +46 -23
- package/dist/client/index.d.ts +105 -85
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +26 -7
- package/dist/component/_generated/api.d.ts +44 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/{src → dist}/component/_generated/api.js +10 -3
- package/dist/component/_generated/component.d.ts +259 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +9 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +10 -0
- package/{src → dist}/component/_generated/server.d.ts +10 -38
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/{src → dist}/component/_generated/server.js +9 -22
- package/dist/component/convex.config.d.ts.map +1 -1
- package/dist/component/convex.config.js +0 -22
- package/dist/component/helpers.d.ts +1 -1
- package/dist/component/helpers.d.ts.map +1 -1
- package/dist/component/helpers.js +1 -2
- package/dist/component/http.d.ts.map +1 -1
- package/dist/component/http.js +0 -1
- package/dist/component/lib.d.ts +7 -0
- package/dist/component/lib.d.ts.map +1 -1
- package/dist/component/lib.js +62 -20
- package/dist/component/schema.d.ts +2 -2
- package/dist/component/tables/contacts.d.ts.map +1 -1
- package/dist/component/tables/emailOperations.d.ts +4 -4
- package/dist/component/tables/emailOperations.d.ts.map +1 -1
- package/dist/test.d.ts +83 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +16 -0
- package/dist/utils.d.ts +6 -6
- package/package.json +15 -9
- package/src/client/index.ts +31 -14
- package/src/component/_generated/api.ts +60 -0
- package/src/component/_generated/component.ts +323 -0
- package/src/component/_generated/{dataModel.d.ts → dataModel.ts} +1 -1
- package/src/component/_generated/server.ts +161 -0
- package/src/component/convex.config.ts +0 -27
- package/src/component/helpers.ts +2 -2
- package/src/component/http.ts +0 -4
- package/src/component/lib.ts +69 -20
- package/src/test.ts +27 -0
- package/dist/client/types.d.ts +0 -24
- package/dist/client/types.d.ts.map +0 -1
- package/dist/client/types.js +0 -0
- package/src/component/_generated/api.d.ts +0 -47
|
@@ -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
|
|
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"}
|
package/dist/component/lib.js
CHANGED
|
@@ -107,6 +107,10 @@ export const logEmailOperation = zm({
|
|
|
107
107
|
/**
|
|
108
108
|
* Count contacts in the database
|
|
109
109
|
* Can filter by audience criteria (userGroup, source, subscribed status)
|
|
110
|
+
*
|
|
111
|
+
* Note: When multiple filters are provided, only one index can be used.
|
|
112
|
+
* Additional filters are applied in-memory, which is efficient for small result sets.
|
|
113
|
+
* For large contact lists with multiple filters, consider using a composite index.
|
|
110
114
|
*/
|
|
111
115
|
export const countContacts = zq({
|
|
112
116
|
args: z.object({
|
|
@@ -116,6 +120,8 @@ export const countContacts = zq({
|
|
|
116
120
|
}),
|
|
117
121
|
returns: z.number(),
|
|
118
122
|
handler: async (ctx, args) => {
|
|
123
|
+
// Build query using the most selective index available
|
|
124
|
+
// Priority: userGroup > source > subscribed
|
|
119
125
|
let contacts;
|
|
120
126
|
if (args.userGroup !== undefined) {
|
|
121
127
|
contacts = await ctx.db
|
|
@@ -138,22 +144,37 @@ export const countContacts = zq({
|
|
|
138
144
|
else {
|
|
139
145
|
contacts = await ctx.db.query("contacts").collect();
|
|
140
146
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
147
|
+
// Apply additional filters if multiple criteria were provided
|
|
148
|
+
// This avoids redundant filtering when only one filter was used
|
|
149
|
+
const needsFiltering = (args.userGroup !== undefined ? 1 : 0) +
|
|
150
|
+
(args.source !== undefined ? 1 : 0) +
|
|
151
|
+
(args.subscribed !== undefined ? 1 : 0) >
|
|
152
|
+
1;
|
|
153
|
+
if (!needsFiltering) {
|
|
154
|
+
return contacts.length;
|
|
146
155
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
156
|
+
const filtered = contacts.filter((c) => {
|
|
157
|
+
if (args.userGroup !== undefined && c.userGroup !== args.userGroup) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
if (args.source !== undefined && c.source !== args.source) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
if (args.subscribed !== undefined && c.subscribed !== args.subscribed) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
});
|
|
168
|
+
return filtered.length;
|
|
151
169
|
},
|
|
152
170
|
});
|
|
153
171
|
/**
|
|
154
172
|
* List contacts from the database with pagination
|
|
155
173
|
* Can filter by audience criteria (userGroup, source, subscribed status)
|
|
156
174
|
* Returns actual contact data, not just a count
|
|
175
|
+
*
|
|
176
|
+
* Note: When multiple filters are provided, only one index can be used.
|
|
177
|
+
* Additional filters are applied in-memory before pagination.
|
|
157
178
|
*/
|
|
158
179
|
export const listContacts = zq({
|
|
159
180
|
args: z.object({
|
|
@@ -183,8 +204,8 @@ export const listContacts = zq({
|
|
|
183
204
|
hasMore: z.boolean(),
|
|
184
205
|
}),
|
|
185
206
|
handler: async (ctx, args) => {
|
|
207
|
+
// Build query using the most selective index available
|
|
186
208
|
let allContacts;
|
|
187
|
-
// Get all contacts matching the filters
|
|
188
209
|
if (args.userGroup !== undefined) {
|
|
189
210
|
allContacts = await ctx.db
|
|
190
211
|
.query("contacts")
|
|
@@ -206,15 +227,24 @@ export const listContacts = zq({
|
|
|
206
227
|
else {
|
|
207
228
|
allContacts = await ctx.db.query("contacts").collect();
|
|
208
229
|
}
|
|
209
|
-
// Apply additional filters
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
230
|
+
// Apply additional filters if multiple criteria were provided
|
|
231
|
+
const needsFiltering = (args.userGroup !== undefined ? 1 : 0) +
|
|
232
|
+
(args.source !== undefined ? 1 : 0) +
|
|
233
|
+
(args.subscribed !== undefined ? 1 : 0) >
|
|
234
|
+
1;
|
|
235
|
+
if (needsFiltering) {
|
|
236
|
+
allContacts = allContacts.filter((c) => {
|
|
237
|
+
if (args.userGroup !== undefined && c.userGroup !== args.userGroup) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
if (args.source !== undefined && c.source !== args.source) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
if (args.subscribed !== undefined && c.subscribed !== args.subscribed) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
return true;
|
|
247
|
+
});
|
|
218
248
|
}
|
|
219
249
|
// Sort by createdAt (newest first)
|
|
220
250
|
allContacts.sort((a, b) => b.createdAt - a.createdAt);
|
|
@@ -433,7 +463,7 @@ export const sendEvent = za({
|
|
|
433
463
|
returns: z.object({
|
|
434
464
|
success: z.boolean(),
|
|
435
465
|
}),
|
|
436
|
-
handler: async (
|
|
466
|
+
handler: async (ctx, args) => {
|
|
437
467
|
const response = await loopsFetch(args.apiKey, "/events/send", {
|
|
438
468
|
method: "POST",
|
|
439
469
|
json: {
|
|
@@ -445,8 +475,20 @@ export const sendEvent = za({
|
|
|
445
475
|
if (!response.ok) {
|
|
446
476
|
const errorText = await response.text();
|
|
447
477
|
console.error(`Loops API error [${response.status}]:`, errorText);
|
|
478
|
+
await ctx.runMutation(internalLib.logEmailOperation, {
|
|
479
|
+
operationType: "event",
|
|
480
|
+
email: args.email,
|
|
481
|
+
success: false,
|
|
482
|
+
eventName: args.eventName,
|
|
483
|
+
});
|
|
448
484
|
throw sanitizeLoopsError(response.status, errorText);
|
|
449
485
|
}
|
|
486
|
+
await ctx.runMutation(internalLib.logEmailOperation, {
|
|
487
|
+
operationType: "event",
|
|
488
|
+
email: args.email,
|
|
489
|
+
success: true,
|
|
490
|
+
eventName: args.eventName,
|
|
491
|
+
});
|
|
450
492
|
return { success: true };
|
|
451
493
|
},
|
|
452
494
|
});
|
|
@@ -29,13 +29,13 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
29
29
|
subscribed: ["subscribed", "_creationTime"];
|
|
30
30
|
}, {}, {}>;
|
|
31
31
|
emailOperations: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
32
|
+
metadata?: Record<string, any> | undefined;
|
|
32
33
|
actorId?: string | undefined;
|
|
33
34
|
transactionalId?: string | undefined;
|
|
34
35
|
campaignId?: string | undefined;
|
|
35
36
|
loopId?: string | undefined;
|
|
36
37
|
eventName?: string | undefined;
|
|
37
38
|
messageId?: string | undefined;
|
|
38
|
-
metadata?: Record<string, any> | undefined;
|
|
39
39
|
email: string;
|
|
40
40
|
success: boolean;
|
|
41
41
|
operationType: "transactional" | "event" | "campaign" | "loop";
|
|
@@ -57,7 +57,7 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
57
57
|
success: import("zod").ZodBoolean;
|
|
58
58
|
messageId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
59
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" |
|
|
60
|
+
}>, "required", "email" | "success" | "metadata" | "operationType" | "actorId" | "transactionalId" | "campaignId" | "loopId" | "eventName" | "timestamp" | "messageId" | `metadata.${string}`>, {
|
|
61
61
|
email: ["email", "timestamp", "_creationTime"];
|
|
62
62
|
actorId: ["actorId", "timestamp", "_creationTime"];
|
|
63
63
|
operationType: ["operationType", "timestamp", "_creationTime"];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contacts.d.ts","sourceRoot":"","sources":["../../../src/component/tables/contacts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,QAAQ
|
|
1
|
+
{"version":3,"file":"contacts.d.ts","sourceRoot":"","sources":["../../../src/component/tables/contacts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWnB,CAAC"}
|
|
@@ -2,13 +2,13 @@ import { z } from "zod";
|
|
|
2
2
|
export declare const EmailOperations: {
|
|
3
3
|
name: "emailOperations";
|
|
4
4
|
table: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
5
|
+
metadata?: Record<string, any> | undefined;
|
|
5
6
|
actorId?: string | undefined;
|
|
6
7
|
transactionalId?: string | undefined;
|
|
7
8
|
campaignId?: string | undefined;
|
|
8
9
|
loopId?: string | undefined;
|
|
9
10
|
eventName?: string | undefined;
|
|
10
11
|
messageId?: string | undefined;
|
|
11
|
-
metadata?: Record<string, any> | undefined;
|
|
12
12
|
email: string;
|
|
13
13
|
success: boolean;
|
|
14
14
|
operationType: "transactional" | "event" | "campaign" | "loop";
|
|
@@ -30,15 +30,15 @@ export declare const EmailOperations: {
|
|
|
30
30
|
success: z.ZodBoolean;
|
|
31
31
|
messageId: z.ZodOptional<z.ZodString>;
|
|
32
32
|
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
33
|
-
}>, "required", "email" | "success" | "operationType" | "actorId" | "transactionalId" | "campaignId" | "loopId" | "eventName" | "timestamp" | "messageId" |
|
|
33
|
+
}>, "required", "email" | "success" | "metadata" | "operationType" | "actorId" | "transactionalId" | "campaignId" | "loopId" | "eventName" | "timestamp" | "messageId" | `metadata.${string}`>, {}, {}, {}>;
|
|
34
34
|
doc: import("convex/values").VObject<{
|
|
35
|
+
metadata?: Record<string, any> | undefined;
|
|
35
36
|
actorId?: string | undefined;
|
|
36
37
|
transactionalId?: string | undefined;
|
|
37
38
|
campaignId?: string | undefined;
|
|
38
39
|
loopId?: string | undefined;
|
|
39
40
|
eventName?: string | undefined;
|
|
40
41
|
messageId?: string | undefined;
|
|
41
|
-
metadata?: Record<string, any> | undefined;
|
|
42
42
|
email: string;
|
|
43
43
|
success: boolean;
|
|
44
44
|
operationType: "transactional" | "event" | "campaign" | "loop";
|
|
@@ -59,7 +59,7 @@ export declare const EmailOperations: {
|
|
|
59
59
|
metadata: import("convex/values").VRecord<Record<string, any> | undefined, import("convex/values").VString<string, "required">, import("convex/values").VAny<"required", "required", string>, "optional", string>;
|
|
60
60
|
_id: import("convex/values").VId<import("convex/values").GenericId<"emailOperations">, "required">;
|
|
61
61
|
_creationTime: import("convex/values").VFloat64<number, "required">;
|
|
62
|
-
}, "required", "email" | "success" | "operationType" | "actorId" | "transactionalId" | "campaignId" | "loopId" | "eventName" | "timestamp" | "messageId" | "
|
|
62
|
+
}, "required", "email" | "success" | "metadata" | "operationType" | "actorId" | "transactionalId" | "campaignId" | "loopId" | "eventName" | "timestamp" | "messageId" | "_creationTime" | `metadata.${string}` | "_id">;
|
|
63
63
|
withoutSystemFields: import("zodvex").ConvexValidatorFromZodFieldsAuto<{
|
|
64
64
|
operationType: z.ZodEnum<{
|
|
65
65
|
transactional: "transactional";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emailOperations.d.ts","sourceRoot":"","sources":["../../../src/component/tables/emailOperations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,eAAe
|
|
1
|
+
{"version":3,"file":"emailOperations.d.ts","sourceRoot":"","sources":["../../../src/component/tables/emailOperations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAY1B,CAAC"}
|
package/dist/test.d.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { TestConvex } from "convex-test";
|
|
2
|
+
import type { GenericSchema, SchemaDefinition } from "convex/server";
|
|
3
|
+
import schema from "./component/schema.js";
|
|
4
|
+
/**
|
|
5
|
+
* Register the Loops component with the test convex instance.
|
|
6
|
+
*
|
|
7
|
+
* @param t - The test convex instance from convexTest().
|
|
8
|
+
* @param name - The name of the component as registered in convex.config.ts.
|
|
9
|
+
* @param modules - The modules object from import.meta.glob. Required.
|
|
10
|
+
*/
|
|
11
|
+
export declare function register(t: TestConvex<SchemaDefinition<GenericSchema, boolean>>, name?: string, modules?: Record<string, () => Promise<unknown>>): void;
|
|
12
|
+
export { schema };
|
|
13
|
+
declare const _default: {
|
|
14
|
+
register: typeof register;
|
|
15
|
+
schema: SchemaDefinition<{
|
|
16
|
+
contacts: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
17
|
+
firstName?: string | undefined;
|
|
18
|
+
lastName?: string | undefined;
|
|
19
|
+
userId?: string | undefined;
|
|
20
|
+
source?: string | undefined;
|
|
21
|
+
subscribed?: boolean | undefined;
|
|
22
|
+
userGroup?: string | undefined;
|
|
23
|
+
loopsContactId?: string | undefined;
|
|
24
|
+
email: string;
|
|
25
|
+
createdAt: number;
|
|
26
|
+
updatedAt: number;
|
|
27
|
+
}, import("zodvex").ConvexValidatorFromZodFieldsAuto<{
|
|
28
|
+
email: import("zod").ZodString;
|
|
29
|
+
firstName: import("zod").ZodOptional<import("zod").ZodString>;
|
|
30
|
+
lastName: import("zod").ZodOptional<import("zod").ZodString>;
|
|
31
|
+
userId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
32
|
+
source: import("zod").ZodOptional<import("zod").ZodString>;
|
|
33
|
+
subscribed: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
34
|
+
userGroup: import("zod").ZodOptional<import("zod").ZodString>;
|
|
35
|
+
loopsContactId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
36
|
+
createdAt: import("zod").ZodNumber;
|
|
37
|
+
updatedAt: import("zod").ZodNumber;
|
|
38
|
+
}>, "required", "email" | "firstName" | "lastName" | "userId" | "source" | "subscribed" | "userGroup" | "loopsContactId" | "createdAt" | "updatedAt">, {
|
|
39
|
+
email: ["email", "_creationTime"];
|
|
40
|
+
userId: ["userId", "_creationTime"];
|
|
41
|
+
userGroup: ["userGroup", "_creationTime"];
|
|
42
|
+
source: ["source", "_creationTime"];
|
|
43
|
+
subscribed: ["subscribed", "_creationTime"];
|
|
44
|
+
}, {}, {}>;
|
|
45
|
+
emailOperations: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
46
|
+
metadata?: Record<string, any> | undefined;
|
|
47
|
+
actorId?: string | undefined;
|
|
48
|
+
transactionalId?: string | undefined;
|
|
49
|
+
campaignId?: string | undefined;
|
|
50
|
+
loopId?: string | undefined;
|
|
51
|
+
eventName?: string | undefined;
|
|
52
|
+
messageId?: string | undefined;
|
|
53
|
+
email: string;
|
|
54
|
+
success: boolean;
|
|
55
|
+
operationType: "transactional" | "event" | "campaign" | "loop";
|
|
56
|
+
timestamp: number;
|
|
57
|
+
}, import("zodvex").ConvexValidatorFromZodFieldsAuto<{
|
|
58
|
+
operationType: import("zod").ZodEnum<{
|
|
59
|
+
transactional: "transactional";
|
|
60
|
+
event: "event";
|
|
61
|
+
campaign: "campaign";
|
|
62
|
+
loop: "loop";
|
|
63
|
+
}>;
|
|
64
|
+
email: import("zod").ZodString;
|
|
65
|
+
actorId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
66
|
+
transactionalId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
67
|
+
campaignId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
68
|
+
loopId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
69
|
+
eventName: import("zod").ZodOptional<import("zod").ZodString>;
|
|
70
|
+
timestamp: import("zod").ZodNumber;
|
|
71
|
+
success: import("zod").ZodBoolean;
|
|
72
|
+
messageId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
73
|
+
metadata: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
|
|
74
|
+
}>, "required", "email" | "success" | "metadata" | "operationType" | "actorId" | "transactionalId" | "campaignId" | "loopId" | "eventName" | "timestamp" | "messageId" | `metadata.${string}`>, {
|
|
75
|
+
email: ["email", "timestamp", "_creationTime"];
|
|
76
|
+
actorId: ["actorId", "timestamp", "_creationTime"];
|
|
77
|
+
operationType: ["operationType", "timestamp", "_creationTime"];
|
|
78
|
+
timestamp: ["timestamp", "_creationTime"];
|
|
79
|
+
}, {}, {}>;
|
|
80
|
+
}, true>;
|
|
81
|
+
};
|
|
82
|
+
export default _default;
|
|
83
|
+
//# sourceMappingURL=test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,MAAM,MAAM,uBAAuB,CAAC;AAE3C;;;;;;GAMG;AACH,wBAAgB,QAAQ,CACvB,CAAC,EAAE,UAAU,CAAC,gBAAgB,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,EACvD,IAAI,GAAE,MAAgB,EACtB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC,QAQhD;AAED,OAAO,EAAE,MAAM,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAElB,wBAAoC"}
|
package/dist/test.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import schema from "./component/schema.js";
|
|
2
|
+
/**
|
|
3
|
+
* Register the Loops component with the test convex instance.
|
|
4
|
+
*
|
|
5
|
+
* @param t - The test convex instance from convexTest().
|
|
6
|
+
* @param name - The name of the component as registered in convex.config.ts.
|
|
7
|
+
* @param modules - The modules object from import.meta.glob. Required.
|
|
8
|
+
*/
|
|
9
|
+
export function register(t, name = "loops", modules) {
|
|
10
|
+
if (!modules) {
|
|
11
|
+
throw new Error("modules parameter is required. Pass import.meta.glob from your test file.");
|
|
12
|
+
}
|
|
13
|
+
t.registerComponent(name, schema, modules);
|
|
14
|
+
}
|
|
15
|
+
export { schema };
|
|
16
|
+
export default { register, schema };
|
package/dist/utils.d.ts
CHANGED
|
@@ -33,19 +33,19 @@ export declare const zq: <A extends import("zod").ZodType | Record<string, impor
|
|
|
33
33
|
document: {
|
|
34
34
|
_id: import("convex/values").GenericId<"emailOperations">;
|
|
35
35
|
_creationTime: number;
|
|
36
|
+
metadata?: Record<string, any> | undefined;
|
|
36
37
|
actorId?: string | undefined;
|
|
37
38
|
transactionalId?: string | undefined;
|
|
38
39
|
campaignId?: string | undefined;
|
|
39
40
|
loopId?: string | undefined;
|
|
40
41
|
eventName?: string | undefined;
|
|
41
42
|
messageId?: string | undefined;
|
|
42
|
-
metadata?: Record<string, any> | undefined;
|
|
43
43
|
email: string;
|
|
44
44
|
success: boolean;
|
|
45
45
|
operationType: "transactional" | "event" | "campaign" | "loop";
|
|
46
46
|
timestamp: number;
|
|
47
47
|
};
|
|
48
|
-
fieldPaths: ("email" | "success" | "operationType" | "actorId" | "transactionalId" | "campaignId" | "loopId" | "eventName" | "timestamp" | "messageId" | "
|
|
48
|
+
fieldPaths: ("email" | "success" | "metadata" | "operationType" | "actorId" | "transactionalId" | "campaignId" | "loopId" | "eventName" | "timestamp" | "messageId" | "_creationTime" | `metadata.${string}`) | "_id";
|
|
49
49
|
indexes: {
|
|
50
50
|
email: ["email", "timestamp", "_creationTime"];
|
|
51
51
|
actorId: ["actorId", "timestamp", "_creationTime"];
|
|
@@ -95,19 +95,19 @@ export declare const zm: <A extends import("zod").ZodType | Record<string, impor
|
|
|
95
95
|
document: {
|
|
96
96
|
_id: import("convex/values").GenericId<"emailOperations">;
|
|
97
97
|
_creationTime: number;
|
|
98
|
+
metadata?: Record<string, any> | undefined;
|
|
98
99
|
actorId?: string | undefined;
|
|
99
100
|
transactionalId?: string | undefined;
|
|
100
101
|
campaignId?: string | undefined;
|
|
101
102
|
loopId?: string | undefined;
|
|
102
103
|
eventName?: string | undefined;
|
|
103
104
|
messageId?: string | undefined;
|
|
104
|
-
metadata?: Record<string, any> | undefined;
|
|
105
105
|
email: string;
|
|
106
106
|
success: boolean;
|
|
107
107
|
operationType: "transactional" | "event" | "campaign" | "loop";
|
|
108
108
|
timestamp: number;
|
|
109
109
|
};
|
|
110
|
-
fieldPaths: ("email" | "success" | "operationType" | "actorId" | "transactionalId" | "campaignId" | "loopId" | "eventName" | "timestamp" | "messageId" | "
|
|
110
|
+
fieldPaths: ("email" | "success" | "metadata" | "operationType" | "actorId" | "transactionalId" | "campaignId" | "loopId" | "eventName" | "timestamp" | "messageId" | "_creationTime" | `metadata.${string}`) | "_id";
|
|
111
111
|
indexes: {
|
|
112
112
|
email: ["email", "timestamp", "_creationTime"];
|
|
113
113
|
actorId: ["actorId", "timestamp", "_creationTime"];
|
|
@@ -157,19 +157,19 @@ export declare const za: <A extends import("zod").ZodType | Record<string, impor
|
|
|
157
157
|
document: {
|
|
158
158
|
_id: import("convex/values").GenericId<"emailOperations">;
|
|
159
159
|
_creationTime: number;
|
|
160
|
+
metadata?: Record<string, any> | undefined;
|
|
160
161
|
actorId?: string | undefined;
|
|
161
162
|
transactionalId?: string | undefined;
|
|
162
163
|
campaignId?: string | undefined;
|
|
163
164
|
loopId?: string | undefined;
|
|
164
165
|
eventName?: string | undefined;
|
|
165
166
|
messageId?: string | undefined;
|
|
166
|
-
metadata?: Record<string, any> | undefined;
|
|
167
167
|
email: string;
|
|
168
168
|
success: boolean;
|
|
169
169
|
operationType: "transactional" | "event" | "campaign" | "loop";
|
|
170
170
|
timestamp: number;
|
|
171
171
|
};
|
|
172
|
-
fieldPaths: ("email" | "success" | "operationType" | "actorId" | "transactionalId" | "campaignId" | "loopId" | "eventName" | "timestamp" | "messageId" | "
|
|
172
|
+
fieldPaths: ("email" | "success" | "metadata" | "operationType" | "actorId" | "transactionalId" | "campaignId" | "loopId" | "eventName" | "timestamp" | "messageId" | "_creationTime" | `metadata.${string}`) | "_id";
|
|
173
173
|
indexes: {
|
|
174
174
|
email: ["email", "timestamp", "_creationTime"];
|
|
175
175
|
actorId: ["actorId", "timestamp", "_creationTime"];
|
package/package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devwithbobby/loops",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "Convex component for integrating with Loops.so email marketing platform",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"convex",
|
|
9
9
|
"component",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
10
|
+
"loops",
|
|
11
|
+
"loops.so",
|
|
12
|
+
"email",
|
|
13
|
+
"email-marketing",
|
|
14
|
+
"transactional-email",
|
|
15
|
+
"newsletter",
|
|
14
16
|
"typescript"
|
|
15
17
|
],
|
|
16
18
|
"repository": {
|
|
@@ -38,7 +40,13 @@
|
|
|
38
40
|
"@convex-dev/component-source": "./src/component/convex.config.ts",
|
|
39
41
|
"types": "./dist/component/convex.config.d.ts",
|
|
40
42
|
"default": "./src/component/convex.config.ts"
|
|
41
|
-
}
|
|
43
|
+
},
|
|
44
|
+
"./component": {
|
|
45
|
+
"@convex-dev/component-source": "./src/component/_generated/component.ts",
|
|
46
|
+
"types": "./dist/component/_generated/component.d.ts",
|
|
47
|
+
"default": "./dist/component/_generated/component.js"
|
|
48
|
+
},
|
|
49
|
+
"./test": "./src/test.ts"
|
|
42
50
|
},
|
|
43
51
|
"scripts": {
|
|
44
52
|
"dev": "run-p -r 'dev:backend' 'dev:frontend' 'build:watch'",
|
|
@@ -73,9 +81,7 @@
|
|
|
73
81
|
"zodvex": "^0.2.3"
|
|
74
82
|
},
|
|
75
83
|
"peerDependencies": {
|
|
76
|
-
"convex": "^1.0.0"
|
|
77
|
-
"react": "^18.0.0 || ^19.0.0",
|
|
78
|
-
"typescript": "^5.9.3"
|
|
84
|
+
"convex": "^1.0.0"
|
|
79
85
|
},
|
|
80
86
|
"devDependencies": {
|
|
81
87
|
"@arethetypeswrong/cli": "^0.18.2",
|
package/src/client/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { actionGeneric, queryGeneric } from "convex/server";
|
|
2
2
|
import { v } from "convex/values";
|
|
3
|
-
import type {
|
|
4
|
-
import type { RunActionCtx, RunQueryCtx
|
|
3
|
+
import type { ComponentApi } from "../component/_generated/component.js";
|
|
4
|
+
import type { RunActionCtx, RunQueryCtx } from "../types";
|
|
5
5
|
|
|
6
|
-
export type LoopsComponent =
|
|
6
|
+
export type LoopsComponent = ComponentApi;
|
|
7
7
|
|
|
8
8
|
export interface ContactData {
|
|
9
9
|
email: string;
|
|
@@ -32,6 +32,7 @@ export class Loops {
|
|
|
32
32
|
apiKey?: string;
|
|
33
33
|
};
|
|
34
34
|
private readonly lib: NonNullable<LoopsComponent["lib"]>;
|
|
35
|
+
private _apiKey?: string;
|
|
35
36
|
|
|
36
37
|
constructor(
|
|
37
38
|
component: LoopsComponent,
|
|
@@ -58,26 +59,30 @@ export class Loops {
|
|
|
58
59
|
|
|
59
60
|
this.lib = component.lib;
|
|
60
61
|
this.options = options;
|
|
61
|
-
|
|
62
|
-
const apiKey = options?.apiKey ?? process.env.LOOPS_API_KEY;
|
|
63
|
-
if (!apiKey) {
|
|
64
|
-
throw new Error(
|
|
65
|
-
"Loops API key is required. Set LOOPS_API_KEY in your Convex environment variables.",
|
|
66
|
-
);
|
|
67
|
-
}
|
|
62
|
+
this._apiKey = options?.apiKey;
|
|
68
63
|
|
|
69
64
|
if (options?.apiKey) {
|
|
70
65
|
console.warn(
|
|
71
66
|
"API key passed directly via options. " +
|
|
72
67
|
"For security, use LOOPS_API_KEY environment variable instead. " +
|
|
73
|
-
"See
|
|
68
|
+
"See README.md for details.",
|
|
74
69
|
);
|
|
75
70
|
}
|
|
76
|
-
|
|
77
|
-
this.apiKey = apiKey;
|
|
78
71
|
}
|
|
79
72
|
|
|
80
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Get the API key, checking environment at call time (not constructor time).
|
|
75
|
+
* This allows the Loops client to be instantiated at module load time.
|
|
76
|
+
*/
|
|
77
|
+
private get apiKey(): string {
|
|
78
|
+
const key = this._apiKey ?? process.env.LOOPS_API_KEY;
|
|
79
|
+
if (!key) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
"Loops API key is required. Set LOOPS_API_KEY in your Convex environment variables.",
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return key;
|
|
85
|
+
}
|
|
81
86
|
|
|
82
87
|
/**
|
|
83
88
|
* Add or update a contact in Loops
|
|
@@ -480,6 +485,18 @@ export class Loops {
|
|
|
480
485
|
return await this.countContacts(ctx, args);
|
|
481
486
|
},
|
|
482
487
|
}),
|
|
488
|
+
listContacts: queryGeneric({
|
|
489
|
+
args: {
|
|
490
|
+
userGroup: v.optional(v.string()),
|
|
491
|
+
source: v.optional(v.string()),
|
|
492
|
+
subscribed: v.optional(v.boolean()),
|
|
493
|
+
limit: v.optional(v.number()),
|
|
494
|
+
offset: v.optional(v.number()),
|
|
495
|
+
},
|
|
496
|
+
handler: async (ctx, args) => {
|
|
497
|
+
return await this.listContacts(ctx, args);
|
|
498
|
+
},
|
|
499
|
+
}),
|
|
483
500
|
detectRecipientSpam: queryGeneric({
|
|
484
501
|
args: {
|
|
485
502
|
timeWindowMs: v.optional(v.number()),
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/**
|
|
3
|
+
* Generated `api` utility.
|
|
4
|
+
*
|
|
5
|
+
* THIS CODE IS AUTOMATICALLY GENERATED.
|
|
6
|
+
*
|
|
7
|
+
* To regenerate, run `npx convex dev`.
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type * as helpers from "../helpers.js";
|
|
12
|
+
import type * as http from "../http.js";
|
|
13
|
+
import type * as lib from "../lib.js";
|
|
14
|
+
import type * as tables_contacts from "../tables/contacts.js";
|
|
15
|
+
import type * as tables_emailOperations from "../tables/emailOperations.js";
|
|
16
|
+
import type * as validators from "../validators.js";
|
|
17
|
+
|
|
18
|
+
import type {
|
|
19
|
+
ApiFromModules,
|
|
20
|
+
FilterApi,
|
|
21
|
+
FunctionReference,
|
|
22
|
+
} from "convex/server";
|
|
23
|
+
import { anyApi, componentsGeneric } from "convex/server";
|
|
24
|
+
|
|
25
|
+
const fullApi: ApiFromModules<{
|
|
26
|
+
helpers: typeof helpers;
|
|
27
|
+
http: typeof http;
|
|
28
|
+
lib: typeof lib;
|
|
29
|
+
"tables/contacts": typeof tables_contacts;
|
|
30
|
+
"tables/emailOperations": typeof tables_emailOperations;
|
|
31
|
+
validators: typeof validators;
|
|
32
|
+
}> = anyApi as any;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A utility for referencing Convex functions in your app's public API.
|
|
36
|
+
*
|
|
37
|
+
* Usage:
|
|
38
|
+
* ```js
|
|
39
|
+
* const myFunctionReference = api.myModule.myFunction;
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export const api: FilterApi<
|
|
43
|
+
typeof fullApi,
|
|
44
|
+
FunctionReference<any, "public">
|
|
45
|
+
> = anyApi as any;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A utility for referencing Convex functions in your app's internal API.
|
|
49
|
+
*
|
|
50
|
+
* Usage:
|
|
51
|
+
* ```js
|
|
52
|
+
* const myFunctionReference = internal.myModule.myFunction;
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export const internal: FilterApi<
|
|
56
|
+
typeof fullApi,
|
|
57
|
+
FunctionReference<any, "internal">
|
|
58
|
+
> = anyApi as any;
|
|
59
|
+
|
|
60
|
+
export const components = componentsGeneric() as unknown as {};
|