@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.
- package/dist/client/index.d.ts +133 -103
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +55 -10
- package/dist/component/_generated/api.d.ts +228 -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 +266 -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/aggregates.d.ts +42 -0
- package/dist/component/aggregates.d.ts.map +1 -0
- package/dist/component/aggregates.js +54 -0
- package/dist/component/convex.config.d.ts.map +1 -1
- package/dist/component/convex.config.js +2 -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.js +1 -1
- package/dist/component/lib.d.ts +66 -17
- package/dist/component/lib.d.ts.map +1 -1
- package/dist/component/lib.js +194 -73
- 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/types.d.ts +249 -62
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -2
- package/dist/utils.d.ts +6 -6
- package/package.json +25 -13
- package/src/client/index.ts +69 -18
- package/src/component/_generated/api.ts +249 -0
- package/src/component/_generated/component.ts +328 -0
- package/src/component/_generated/server.ts +161 -0
- package/src/component/aggregates.ts +89 -0
- package/src/component/convex.config.ts +2 -26
- package/src/component/helpers.ts +2 -2
- package/src/component/http.ts +1 -1
- package/src/component/lib.ts +226 -89
- package/src/test.ts +27 -0
- package/src/types.ts +20 -122
- package/src/component/_generated/api.d.ts +0 -47
- /package/src/component/_generated/{dataModel.d.ts → dataModel.ts} +0 -0
package/dist/client/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { RunActionCtx,
|
|
3
|
-
export type LoopsComponent =
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
email?: string | null
|
|
69
|
-
firstName?: string | null
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
results?: {
|
|
88
|
+
created?: number;
|
|
89
|
+
failed?: number;
|
|
90
|
+
results?: Array<{
|
|
87
91
|
email: string;
|
|
92
|
+
error?: string;
|
|
88
93
|
success: boolean;
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
196
|
+
email?: string;
|
|
194
197
|
firstTimestamp: number;
|
|
195
198
|
lastTimestamp: number;
|
|
196
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
331
|
-
email?: string | null
|
|
332
|
-
firstName?: string | null
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
results?: {
|
|
380
|
+
created?: number;
|
|
381
|
+
failed?: number;
|
|
382
|
+
results?: Array<{
|
|
356
383
|
email: string;
|
|
384
|
+
error?: string;
|
|
357
385
|
success: boolean;
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
|
|
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
|
-
|
|
459
|
+
email?: string;
|
|
437
460
|
firstTimestamp: number;
|
|
438
461
|
lastTimestamp: number;
|
|
439
|
-
|
|
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,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"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"}
|
package/dist/client/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|