@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/src/client/index.ts
CHANGED
|
@@ -1,9 +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
|
-
import type {
|
|
4
|
-
import type { RunActionCtx,
|
|
3
|
+
import type { ComponentApi } from "../component/_generated/component.js";
|
|
4
|
+
import type { RunActionCtx, RunMutationCtx, 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,13 +59,7 @@ 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(
|
|
@@ -73,11 +68,21 @@ export class Loops {
|
|
|
73
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
|
|
@@ -190,9 +195,12 @@ export class Loops {
|
|
|
190
195
|
}
|
|
191
196
|
|
|
192
197
|
/**
|
|
193
|
-
* List contacts with pagination and optional filters
|
|
198
|
+
* List contacts with cursor-based pagination and optional filters
|
|
194
199
|
* Returns actual contact data, not just a count
|
|
195
200
|
* This queries the component's local database, not Loops API
|
|
201
|
+
*
|
|
202
|
+
* Uses cursor-based pagination for efficiency. Pass the `continueCursor`
|
|
203
|
+
* from the previous response as `cursor` to get the next page.
|
|
196
204
|
*/
|
|
197
205
|
async listContacts(
|
|
198
206
|
ctx: RunQueryCtx,
|
|
@@ -201,7 +209,7 @@ export class Loops {
|
|
|
201
209
|
source?: string;
|
|
202
210
|
subscribed?: boolean;
|
|
203
211
|
limit?: number;
|
|
204
|
-
|
|
212
|
+
cursor?: string | null;
|
|
205
213
|
},
|
|
206
214
|
) {
|
|
207
215
|
return ctx.runQuery(this.lib.listContacts, {
|
|
@@ -209,7 +217,7 @@ export class Loops {
|
|
|
209
217
|
source: options?.source,
|
|
210
218
|
subscribed: options?.subscribed,
|
|
211
219
|
limit: options?.limit ?? 100,
|
|
212
|
-
|
|
220
|
+
cursor: options?.cursor ?? null,
|
|
213
221
|
});
|
|
214
222
|
}
|
|
215
223
|
|
|
@@ -351,6 +359,39 @@ export class Loops {
|
|
|
351
359
|
});
|
|
352
360
|
}
|
|
353
361
|
|
|
362
|
+
/**
|
|
363
|
+
* Backfill the contact aggregate with existing contacts.
|
|
364
|
+
* Run this after upgrading to a version with aggregate support.
|
|
365
|
+
*
|
|
366
|
+
* This processes contacts in batches to avoid timeout issues with large datasets.
|
|
367
|
+
* Call repeatedly with the returned cursor until isDone is true.
|
|
368
|
+
*
|
|
369
|
+
* Usage:
|
|
370
|
+
* ```ts
|
|
371
|
+
* // First call - clear existing aggregate and start backfill
|
|
372
|
+
* let result = await loops.backfillContactAggregate(ctx, { clear: true });
|
|
373
|
+
*
|
|
374
|
+
* // Continue until done
|
|
375
|
+
* while (!result.isDone) {
|
|
376
|
+
* result = await loops.backfillContactAggregate(ctx, { cursor: result.cursor });
|
|
377
|
+
* }
|
|
378
|
+
* ```
|
|
379
|
+
*/
|
|
380
|
+
async backfillContactAggregate(
|
|
381
|
+
ctx: RunMutationCtx,
|
|
382
|
+
options?: {
|
|
383
|
+
cursor?: string | null;
|
|
384
|
+
batchSize?: number;
|
|
385
|
+
clear?: boolean;
|
|
386
|
+
},
|
|
387
|
+
) {
|
|
388
|
+
return ctx.runMutation(this.lib.backfillContactAggregate, {
|
|
389
|
+
cursor: options?.cursor ?? null,
|
|
390
|
+
batchSize: options?.batchSize ?? 100,
|
|
391
|
+
clear: options?.clear,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
354
395
|
/**
|
|
355
396
|
* For easy re-exporting.
|
|
356
397
|
* Apps can do
|
|
@@ -486,7 +527,7 @@ export class Loops {
|
|
|
486
527
|
source: v.optional(v.string()),
|
|
487
528
|
subscribed: v.optional(v.boolean()),
|
|
488
529
|
limit: v.optional(v.number()),
|
|
489
|
-
|
|
530
|
+
cursor: v.optional(v.union(v.string(), v.null())),
|
|
490
531
|
},
|
|
491
532
|
handler: async (ctx, args) => {
|
|
492
533
|
return await this.listContacts(ctx, args);
|
|
@@ -556,6 +597,16 @@ export class Loops {
|
|
|
556
597
|
return await this.checkGlobalRateLimit(ctx, args);
|
|
557
598
|
},
|
|
558
599
|
}),
|
|
600
|
+
backfillContactAggregate: mutationGeneric({
|
|
601
|
+
args: {
|
|
602
|
+
cursor: v.optional(v.union(v.string(), v.null())),
|
|
603
|
+
batchSize: v.optional(v.number()),
|
|
604
|
+
clear: v.optional(v.boolean()),
|
|
605
|
+
},
|
|
606
|
+
handler: async (ctx, args) => {
|
|
607
|
+
return await this.backfillContactAggregate(ctx, args);
|
|
608
|
+
},
|
|
609
|
+
}),
|
|
559
610
|
};
|
|
560
611
|
}
|
|
561
612
|
}
|
|
@@ -0,0 +1,249 @@
|
|
|
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 aggregates from "../aggregates.js";
|
|
12
|
+
import type * as helpers from "../helpers.js";
|
|
13
|
+
import type * as http from "../http.js";
|
|
14
|
+
import type * as lib from "../lib.js";
|
|
15
|
+
import type * as tables_contacts from "../tables/contacts.js";
|
|
16
|
+
import type * as tables_emailOperations from "../tables/emailOperations.js";
|
|
17
|
+
import type * as validators from "../validators.js";
|
|
18
|
+
|
|
19
|
+
import type {
|
|
20
|
+
ApiFromModules,
|
|
21
|
+
FilterApi,
|
|
22
|
+
FunctionReference,
|
|
23
|
+
} from "convex/server";
|
|
24
|
+
import { anyApi, componentsGeneric } from "convex/server";
|
|
25
|
+
|
|
26
|
+
const fullApi: ApiFromModules<{
|
|
27
|
+
aggregates: typeof aggregates;
|
|
28
|
+
helpers: typeof helpers;
|
|
29
|
+
http: typeof http;
|
|
30
|
+
lib: typeof lib;
|
|
31
|
+
"tables/contacts": typeof tables_contacts;
|
|
32
|
+
"tables/emailOperations": typeof tables_emailOperations;
|
|
33
|
+
validators: typeof validators;
|
|
34
|
+
}> = anyApi as any;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A utility for referencing Convex functions in your app's public API.
|
|
38
|
+
*
|
|
39
|
+
* Usage:
|
|
40
|
+
* ```js
|
|
41
|
+
* const myFunctionReference = api.myModule.myFunction;
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export const api: FilterApi<
|
|
45
|
+
typeof fullApi,
|
|
46
|
+
FunctionReference<any, "public">
|
|
47
|
+
> = anyApi as any;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A utility for referencing Convex functions in your app's internal API.
|
|
51
|
+
*
|
|
52
|
+
* Usage:
|
|
53
|
+
* ```js
|
|
54
|
+
* const myFunctionReference = internal.myModule.myFunction;
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export const internal: FilterApi<
|
|
58
|
+
typeof fullApi,
|
|
59
|
+
FunctionReference<any, "internal">
|
|
60
|
+
> = anyApi as any;
|
|
61
|
+
|
|
62
|
+
export const components = componentsGeneric() as unknown as {
|
|
63
|
+
contactAggregate: {
|
|
64
|
+
btree: {
|
|
65
|
+
aggregateBetween: FunctionReference<
|
|
66
|
+
"query",
|
|
67
|
+
"internal",
|
|
68
|
+
{ k1?: any; k2?: any; namespace?: any },
|
|
69
|
+
{ count: number; sum: number }
|
|
70
|
+
>;
|
|
71
|
+
aggregateBetweenBatch: FunctionReference<
|
|
72
|
+
"query",
|
|
73
|
+
"internal",
|
|
74
|
+
{ queries: Array<{ k1?: any; k2?: any; namespace?: any }> },
|
|
75
|
+
Array<{ count: number; sum: number }>
|
|
76
|
+
>;
|
|
77
|
+
atNegativeOffset: FunctionReference<
|
|
78
|
+
"query",
|
|
79
|
+
"internal",
|
|
80
|
+
{ k1?: any; k2?: any; namespace?: any; offset: number },
|
|
81
|
+
{ k: any; s: number; v: any }
|
|
82
|
+
>;
|
|
83
|
+
atOffset: FunctionReference<
|
|
84
|
+
"query",
|
|
85
|
+
"internal",
|
|
86
|
+
{ k1?: any; k2?: any; namespace?: any; offset: number },
|
|
87
|
+
{ k: any; s: number; v: any }
|
|
88
|
+
>;
|
|
89
|
+
atOffsetBatch: FunctionReference<
|
|
90
|
+
"query",
|
|
91
|
+
"internal",
|
|
92
|
+
{
|
|
93
|
+
queries: Array<{
|
|
94
|
+
k1?: any;
|
|
95
|
+
k2?: any;
|
|
96
|
+
namespace?: any;
|
|
97
|
+
offset: number;
|
|
98
|
+
}>;
|
|
99
|
+
},
|
|
100
|
+
Array<{ k: any; s: number; v: any }>
|
|
101
|
+
>;
|
|
102
|
+
get: FunctionReference<
|
|
103
|
+
"query",
|
|
104
|
+
"internal",
|
|
105
|
+
{ key: any; namespace?: any },
|
|
106
|
+
null | { k: any; s: number; v: any }
|
|
107
|
+
>;
|
|
108
|
+
offset: FunctionReference<
|
|
109
|
+
"query",
|
|
110
|
+
"internal",
|
|
111
|
+
{ k1?: any; key: any; namespace?: any },
|
|
112
|
+
number
|
|
113
|
+
>;
|
|
114
|
+
offsetUntil: FunctionReference<
|
|
115
|
+
"query",
|
|
116
|
+
"internal",
|
|
117
|
+
{ k2?: any; key: any; namespace?: any },
|
|
118
|
+
number
|
|
119
|
+
>;
|
|
120
|
+
paginate: FunctionReference<
|
|
121
|
+
"query",
|
|
122
|
+
"internal",
|
|
123
|
+
{
|
|
124
|
+
cursor?: string;
|
|
125
|
+
k1?: any;
|
|
126
|
+
k2?: any;
|
|
127
|
+
limit: number;
|
|
128
|
+
namespace?: any;
|
|
129
|
+
order: "asc" | "desc";
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
cursor: string;
|
|
133
|
+
isDone: boolean;
|
|
134
|
+
page: Array<{ k: any; s: number; v: any }>;
|
|
135
|
+
}
|
|
136
|
+
>;
|
|
137
|
+
paginateNamespaces: FunctionReference<
|
|
138
|
+
"query",
|
|
139
|
+
"internal",
|
|
140
|
+
{ cursor?: string; limit: number },
|
|
141
|
+
{ cursor: string; isDone: boolean; page: Array<any> }
|
|
142
|
+
>;
|
|
143
|
+
validate: FunctionReference<
|
|
144
|
+
"query",
|
|
145
|
+
"internal",
|
|
146
|
+
{ namespace?: any },
|
|
147
|
+
any
|
|
148
|
+
>;
|
|
149
|
+
};
|
|
150
|
+
inspect: {
|
|
151
|
+
display: FunctionReference<"query", "internal", { namespace?: any }, any>;
|
|
152
|
+
dump: FunctionReference<"query", "internal", { namespace?: any }, string>;
|
|
153
|
+
inspectNode: FunctionReference<
|
|
154
|
+
"query",
|
|
155
|
+
"internal",
|
|
156
|
+
{ namespace?: any; node?: string },
|
|
157
|
+
null
|
|
158
|
+
>;
|
|
159
|
+
listTreeNodes: FunctionReference<
|
|
160
|
+
"query",
|
|
161
|
+
"internal",
|
|
162
|
+
{ take?: number },
|
|
163
|
+
Array<{
|
|
164
|
+
_creationTime: number;
|
|
165
|
+
_id: string;
|
|
166
|
+
aggregate?: { count: number; sum: number };
|
|
167
|
+
items: Array<{ k: any; s: number; v: any }>;
|
|
168
|
+
subtrees: Array<string>;
|
|
169
|
+
}>
|
|
170
|
+
>;
|
|
171
|
+
listTrees: FunctionReference<
|
|
172
|
+
"query",
|
|
173
|
+
"internal",
|
|
174
|
+
{ take?: number },
|
|
175
|
+
Array<{
|
|
176
|
+
_creationTime: number;
|
|
177
|
+
_id: string;
|
|
178
|
+
maxNodeSize: number;
|
|
179
|
+
namespace?: any;
|
|
180
|
+
root: string;
|
|
181
|
+
}>
|
|
182
|
+
>;
|
|
183
|
+
};
|
|
184
|
+
public: {
|
|
185
|
+
clear: FunctionReference<
|
|
186
|
+
"mutation",
|
|
187
|
+
"internal",
|
|
188
|
+
{ maxNodeSize?: number; namespace?: any; rootLazy?: boolean },
|
|
189
|
+
null
|
|
190
|
+
>;
|
|
191
|
+
delete_: FunctionReference<
|
|
192
|
+
"mutation",
|
|
193
|
+
"internal",
|
|
194
|
+
{ key: any; namespace?: any },
|
|
195
|
+
null
|
|
196
|
+
>;
|
|
197
|
+
deleteIfExists: FunctionReference<
|
|
198
|
+
"mutation",
|
|
199
|
+
"internal",
|
|
200
|
+
{ key: any; namespace?: any },
|
|
201
|
+
any
|
|
202
|
+
>;
|
|
203
|
+
init: FunctionReference<
|
|
204
|
+
"mutation",
|
|
205
|
+
"internal",
|
|
206
|
+
{ maxNodeSize?: number; namespace?: any; rootLazy?: boolean },
|
|
207
|
+
null
|
|
208
|
+
>;
|
|
209
|
+
insert: FunctionReference<
|
|
210
|
+
"mutation",
|
|
211
|
+
"internal",
|
|
212
|
+
{ key: any; namespace?: any; summand?: number; value: any },
|
|
213
|
+
null
|
|
214
|
+
>;
|
|
215
|
+
makeRootLazy: FunctionReference<
|
|
216
|
+
"mutation",
|
|
217
|
+
"internal",
|
|
218
|
+
{ namespace?: any },
|
|
219
|
+
null
|
|
220
|
+
>;
|
|
221
|
+
replace: FunctionReference<
|
|
222
|
+
"mutation",
|
|
223
|
+
"internal",
|
|
224
|
+
{
|
|
225
|
+
currentKey: any;
|
|
226
|
+
namespace?: any;
|
|
227
|
+
newKey: any;
|
|
228
|
+
newNamespace?: any;
|
|
229
|
+
summand?: number;
|
|
230
|
+
value: any;
|
|
231
|
+
},
|
|
232
|
+
null
|
|
233
|
+
>;
|
|
234
|
+
replaceOrInsert: FunctionReference<
|
|
235
|
+
"mutation",
|
|
236
|
+
"internal",
|
|
237
|
+
{
|
|
238
|
+
currentKey: any;
|
|
239
|
+
namespace?: any;
|
|
240
|
+
newKey: any;
|
|
241
|
+
newNamespace?: any;
|
|
242
|
+
summand?: number;
|
|
243
|
+
value: any;
|
|
244
|
+
},
|
|
245
|
+
any
|
|
246
|
+
>;
|
|
247
|
+
};
|
|
248
|
+
};
|
|
249
|
+
};
|