@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
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable */
|
|
2
1
|
/**
|
|
3
2
|
* Generated utilities for implementing server-side Convex query and mutation functions.
|
|
4
3
|
*
|
|
@@ -7,27 +6,8 @@
|
|
|
7
6
|
* To regenerate, run `npx convex dev`.
|
|
8
7
|
* @module
|
|
9
8
|
*/
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
ActionBuilder,
|
|
13
|
-
AnyComponents,
|
|
14
|
-
HttpActionBuilder,
|
|
15
|
-
MutationBuilder,
|
|
16
|
-
QueryBuilder,
|
|
17
|
-
GenericActionCtx,
|
|
18
|
-
GenericMutationCtx,
|
|
19
|
-
GenericQueryCtx,
|
|
20
|
-
GenericDatabaseReader,
|
|
21
|
-
GenericDatabaseWriter,
|
|
22
|
-
FunctionReference,
|
|
23
|
-
} from "convex/server";
|
|
9
|
+
import type { ActionBuilder, HttpActionBuilder, MutationBuilder, QueryBuilder, GenericActionCtx, GenericMutationCtx, GenericQueryCtx, GenericDatabaseReader, GenericDatabaseWriter } from "convex/server";
|
|
24
10
|
import type { DataModel } from "./dataModel.js";
|
|
25
|
-
|
|
26
|
-
type GenericCtx =
|
|
27
|
-
| GenericActionCtx<DataModel>
|
|
28
|
-
| GenericMutationCtx<DataModel>
|
|
29
|
-
| GenericQueryCtx<DataModel>;
|
|
30
|
-
|
|
31
11
|
/**
|
|
32
12
|
* Define a query in this Convex app's public API.
|
|
33
13
|
*
|
|
@@ -37,7 +17,6 @@ type GenericCtx =
|
|
|
37
17
|
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
|
38
18
|
*/
|
|
39
19
|
export declare const query: QueryBuilder<DataModel, "public">;
|
|
40
|
-
|
|
41
20
|
/**
|
|
42
21
|
* Define a query that is only accessible from other Convex functions (but not from the client).
|
|
43
22
|
*
|
|
@@ -47,7 +26,6 @@ export declare const query: QueryBuilder<DataModel, "public">;
|
|
|
47
26
|
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
|
48
27
|
*/
|
|
49
28
|
export declare const internalQuery: QueryBuilder<DataModel, "internal">;
|
|
50
|
-
|
|
51
29
|
/**
|
|
52
30
|
* Define a mutation in this Convex app's public API.
|
|
53
31
|
*
|
|
@@ -57,7 +35,6 @@ export declare const internalQuery: QueryBuilder<DataModel, "internal">;
|
|
|
57
35
|
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
|
58
36
|
*/
|
|
59
37
|
export declare const mutation: MutationBuilder<DataModel, "public">;
|
|
60
|
-
|
|
61
38
|
/**
|
|
62
39
|
* Define a mutation that is only accessible from other Convex functions (but not from the client).
|
|
63
40
|
*
|
|
@@ -67,7 +44,6 @@ export declare const mutation: MutationBuilder<DataModel, "public">;
|
|
|
67
44
|
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
|
68
45
|
*/
|
|
69
46
|
export declare const internalMutation: MutationBuilder<DataModel, "internal">;
|
|
70
|
-
|
|
71
47
|
/**
|
|
72
48
|
* Define an action in this Convex app's public API.
|
|
73
49
|
*
|
|
@@ -80,7 +56,6 @@ export declare const internalMutation: MutationBuilder<DataModel, "internal">;
|
|
|
80
56
|
* @returns The wrapped action. Include this as an `export` to name it and make it accessible.
|
|
81
57
|
*/
|
|
82
58
|
export declare const action: ActionBuilder<DataModel, "public">;
|
|
83
|
-
|
|
84
59
|
/**
|
|
85
60
|
* Define an action that is only accessible from other Convex functions (but not from the client).
|
|
86
61
|
*
|
|
@@ -88,38 +63,36 @@ export declare const action: ActionBuilder<DataModel, "public">;
|
|
|
88
63
|
* @returns The wrapped function. Include this as an `export` to name it and make it accessible.
|
|
89
64
|
*/
|
|
90
65
|
export declare const internalAction: ActionBuilder<DataModel, "internal">;
|
|
91
|
-
|
|
92
66
|
/**
|
|
93
67
|
* Define an HTTP action.
|
|
94
68
|
*
|
|
95
|
-
*
|
|
96
|
-
* deployment if the requests matches the path and method where
|
|
97
|
-
* is routed. Be sure to route your
|
|
69
|
+
* The wrapped function will be used to respond to HTTP requests received
|
|
70
|
+
* by a Convex deployment if the requests matches the path and method where
|
|
71
|
+
* this action is routed. Be sure to route your httpAction in `convex/http.js`.
|
|
98
72
|
*
|
|
99
|
-
* @param func - The function. It receives an {@link ActionCtx} as its first argument
|
|
73
|
+
* @param func - The function. It receives an {@link ActionCtx} as its first argument
|
|
74
|
+
* and a Fetch API `Request` object as its second.
|
|
100
75
|
* @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
|
|
101
76
|
*/
|
|
102
77
|
export declare const httpAction: HttpActionBuilder;
|
|
103
|
-
|
|
104
78
|
/**
|
|
105
79
|
* A set of services for use within Convex query functions.
|
|
106
80
|
*
|
|
107
81
|
* The query context is passed as the first argument to any Convex query
|
|
108
82
|
* function run on the server.
|
|
109
83
|
*
|
|
110
|
-
*
|
|
111
|
-
* read-only.
|
|
84
|
+
* If you're using code generation, use the `QueryCtx` type in `convex/_generated/server.d.ts` instead.
|
|
112
85
|
*/
|
|
113
86
|
export type QueryCtx = GenericQueryCtx<DataModel>;
|
|
114
|
-
|
|
115
87
|
/**
|
|
116
88
|
* A set of services for use within Convex mutation functions.
|
|
117
89
|
*
|
|
118
90
|
* The mutation context is passed as the first argument to any Convex mutation
|
|
119
91
|
* function run on the server.
|
|
92
|
+
*
|
|
93
|
+
* If you're using code generation, use the `MutationCtx` type in `convex/_generated/server.d.ts` instead.
|
|
120
94
|
*/
|
|
121
95
|
export type MutationCtx = GenericMutationCtx<DataModel>;
|
|
122
|
-
|
|
123
96
|
/**
|
|
124
97
|
* A set of services for use within Convex action functions.
|
|
125
98
|
*
|
|
@@ -127,7 +100,6 @@ export type MutationCtx = GenericMutationCtx<DataModel>;
|
|
|
127
100
|
* function run on the server.
|
|
128
101
|
*/
|
|
129
102
|
export type ActionCtx = GenericActionCtx<DataModel>;
|
|
130
|
-
|
|
131
103
|
/**
|
|
132
104
|
* An interface to read from the database within Convex query functions.
|
|
133
105
|
*
|
|
@@ -136,7 +108,6 @@ export type ActionCtx = GenericActionCtx<DataModel>;
|
|
|
136
108
|
* building a query.
|
|
137
109
|
*/
|
|
138
110
|
export type DatabaseReader = GenericDatabaseReader<DataModel>;
|
|
139
|
-
|
|
140
111
|
/**
|
|
141
112
|
* An interface to read from and write to the database within Convex mutation
|
|
142
113
|
* functions.
|
|
@@ -147,3 +118,4 @@ export type DatabaseReader = GenericDatabaseReader<DataModel>;
|
|
|
147
118
|
* for the guarantees Convex provides your functions.
|
|
148
119
|
*/
|
|
149
120
|
export type DatabaseWriter = GenericDatabaseWriter<DataModel>;
|
|
121
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/component/_generated/server.ts"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,qBAAqB,EACrB,qBAAqB,EACtB,MAAM,eAAe,CAAC;AAUvB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;;;;;GAOG;AACH,eAAO,MAAM,KAAK,EAAE,YAAY,CAAC,SAAS,EAAE,QAAQ,CAAgB,CAAC;AAErE;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,EAAE,YAAY,CAAC,SAAS,EAAE,UAAU,CACxC,CAAC;AAEvB;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,EAAE,eAAe,CAAC,SAAS,EAAE,QAAQ,CAAmB,CAAC;AAE9E;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,EAAE,eAAe,CAAC,SAAS,EAAE,UAAU,CAC3C,CAAC;AAE1B;;;;;;;;;;GAUG;AACH,eAAO,MAAM,MAAM,EAAE,aAAa,CAAC,SAAS,EAAE,QAAQ,CAAiB,CAAC;AAExE;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,aAAa,CAAC,SAAS,EAAE,UAAU,CACzC,CAAC;AAExB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU,EAAE,iBAAqC,CAAC;AAO/D;;;;;;;GAOG;AACH,MAAM,MAAM,QAAQ,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;AAExD;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;AAE9D;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC"}
|
|
@@ -7,18 +7,7 @@
|
|
|
7
7
|
* To regenerate, run `npx convex dev`.
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
actionGeneric,
|
|
13
|
-
httpActionGeneric,
|
|
14
|
-
queryGeneric,
|
|
15
|
-
mutationGeneric,
|
|
16
|
-
internalActionGeneric,
|
|
17
|
-
internalMutationGeneric,
|
|
18
|
-
internalQueryGeneric,
|
|
19
|
-
componentsGeneric,
|
|
20
|
-
} from "convex/server";
|
|
21
|
-
|
|
10
|
+
import { actionGeneric, httpActionGeneric, queryGeneric, mutationGeneric, internalActionGeneric, internalMutationGeneric, internalQueryGeneric, } from "convex/server";
|
|
22
11
|
/**
|
|
23
12
|
* Define a query in this Convex app's public API.
|
|
24
13
|
*
|
|
@@ -28,7 +17,6 @@ import {
|
|
|
28
17
|
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
|
29
18
|
*/
|
|
30
19
|
export const query = queryGeneric;
|
|
31
|
-
|
|
32
20
|
/**
|
|
33
21
|
* Define a query that is only accessible from other Convex functions (but not from the client).
|
|
34
22
|
*
|
|
@@ -38,7 +26,6 @@ export const query = queryGeneric;
|
|
|
38
26
|
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
|
39
27
|
*/
|
|
40
28
|
export const internalQuery = internalQueryGeneric;
|
|
41
|
-
|
|
42
29
|
/**
|
|
43
30
|
* Define a mutation in this Convex app's public API.
|
|
44
31
|
*
|
|
@@ -48,7 +35,6 @@ export const internalQuery = internalQueryGeneric;
|
|
|
48
35
|
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
|
49
36
|
*/
|
|
50
37
|
export const mutation = mutationGeneric;
|
|
51
|
-
|
|
52
38
|
/**
|
|
53
39
|
* Define a mutation that is only accessible from other Convex functions (but not from the client).
|
|
54
40
|
*
|
|
@@ -58,7 +44,6 @@ export const mutation = mutationGeneric;
|
|
|
58
44
|
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
|
59
45
|
*/
|
|
60
46
|
export const internalMutation = internalMutationGeneric;
|
|
61
|
-
|
|
62
47
|
/**
|
|
63
48
|
* Define an action in this Convex app's public API.
|
|
64
49
|
*
|
|
@@ -71,7 +56,6 @@ export const internalMutation = internalMutationGeneric;
|
|
|
71
56
|
* @returns The wrapped action. Include this as an `export` to name it and make it accessible.
|
|
72
57
|
*/
|
|
73
58
|
export const action = actionGeneric;
|
|
74
|
-
|
|
75
59
|
/**
|
|
76
60
|
* Define an action that is only accessible from other Convex functions (but not from the client).
|
|
77
61
|
*
|
|
@@ -79,12 +63,15 @@ export const action = actionGeneric;
|
|
|
79
63
|
* @returns The wrapped function. Include this as an `export` to name it and make it accessible.
|
|
80
64
|
*/
|
|
81
65
|
export const internalAction = internalActionGeneric;
|
|
82
|
-
|
|
83
66
|
/**
|
|
84
|
-
* Define
|
|
67
|
+
* Define an HTTP action.
|
|
85
68
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
69
|
+
* The wrapped function will be used to respond to HTTP requests received
|
|
70
|
+
* by a Convex deployment if the requests matches the path and method where
|
|
71
|
+
* this action is routed. Be sure to route your httpAction in `convex/http.js`.
|
|
72
|
+
*
|
|
73
|
+
* @param func - The function. It receives an {@link ActionCtx} as its first argument
|
|
74
|
+
* and a Fetch API `Request` object as its second.
|
|
75
|
+
* @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
|
|
89
76
|
*/
|
|
90
77
|
export const httpAction = httpActionGeneric;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { TableAggregate } from "@convex-dev/aggregate";
|
|
2
|
+
import type { GenericMutationCtx, GenericQueryCtx } from "convex/server";
|
|
3
|
+
import type { DataModel, Doc } from "./_generated/dataModel";
|
|
4
|
+
/**
|
|
5
|
+
* Aggregate for counting contacts.
|
|
6
|
+
* Uses userGroup as namespace for efficient filtered counting.
|
|
7
|
+
* Key is null since we only need counts, not ordering.
|
|
8
|
+
*/
|
|
9
|
+
export declare const contactAggregate: TableAggregate<{
|
|
10
|
+
Namespace: string | undefined;
|
|
11
|
+
Key: null;
|
|
12
|
+
DataModel: DataModel;
|
|
13
|
+
TableName: "contacts";
|
|
14
|
+
}>;
|
|
15
|
+
type MutationCtx = GenericMutationCtx<DataModel>;
|
|
16
|
+
type QueryCtx = GenericQueryCtx<DataModel>;
|
|
17
|
+
/**
|
|
18
|
+
* Insert a contact into the aggregate
|
|
19
|
+
*/
|
|
20
|
+
export declare function aggregateInsert(ctx: MutationCtx, doc: Doc<"contacts">): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Delete a contact from the aggregate
|
|
23
|
+
*/
|
|
24
|
+
export declare function aggregateDelete(ctx: MutationCtx, doc: Doc<"contacts">): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Replace a contact in the aggregate (when userGroup changes)
|
|
27
|
+
*/
|
|
28
|
+
export declare function aggregateReplace(ctx: MutationCtx, oldDoc: Doc<"contacts">, newDoc: Doc<"contacts">): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Count contacts by userGroup namespace
|
|
31
|
+
*/
|
|
32
|
+
export declare function aggregateCountByUserGroup(ctx: QueryCtx, userGroup: string | undefined): Promise<number>;
|
|
33
|
+
/**
|
|
34
|
+
* Count all contacts across all userGroups
|
|
35
|
+
*/
|
|
36
|
+
export declare function aggregateCountTotal(ctx: QueryCtx): Promise<number>;
|
|
37
|
+
/**
|
|
38
|
+
* Clear and reinitialize the aggregate (for backfill)
|
|
39
|
+
*/
|
|
40
|
+
export declare function aggregateClear(ctx: MutationCtx, namespace?: string): Promise<void>;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=aggregates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregates.d.ts","sourceRoot":"","sources":["../../src/component/aggregates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAO7D;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;eACjB,MAAM,GAAG,SAAS;SACxB,IAAI;eACE,SAAS;eACT,UAAU;EAIpB,CAAC;AAEH,KAAK,WAAW,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;AACjD,KAAK,QAAQ,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;AAE3C;;GAEG;AACH,wBAAsB,eAAe,CACpC,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE,GAAG,CAAC,UAAU,CAAC,GAClB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;GAEG;AACH,wBAAsB,eAAe,CACpC,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE,GAAG,CAAC,UAAU,CAAC,GAClB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACrC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,GAAG,CAAC,UAAU,CAAC,EACvB,MAAM,EAAE,GAAG,CAAC,UAAU,CAAC,GACrB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAC9C,GAAG,EAAE,QAAQ,EACb,SAAS,EAAE,MAAM,GAAG,SAAS,GAC3B,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAMxE;AAED;;GAEG;AACH,wBAAsB,cAAc,CACnC,GAAG,EAAE,WAAW,EAChB,SAAS,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAEf"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { TableAggregate } from "@convex-dev/aggregate";
|
|
2
|
+
import { components } from "./_generated/api";
|
|
3
|
+
// Cast components to the expected type for the aggregate library
|
|
4
|
+
// biome-ignore lint/suspicious/noExplicitAny: Component API type mismatch with aggregate library
|
|
5
|
+
const contactAggregateComponent = components.contactAggregate;
|
|
6
|
+
/**
|
|
7
|
+
* Aggregate for counting contacts.
|
|
8
|
+
* Uses userGroup as namespace for efficient filtered counting.
|
|
9
|
+
* Key is null since we only need counts, not ordering.
|
|
10
|
+
*/
|
|
11
|
+
export const contactAggregate = new TableAggregate(contactAggregateComponent, {
|
|
12
|
+
namespace: (doc) => doc.userGroup,
|
|
13
|
+
sortKey: () => null,
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Insert a contact into the aggregate
|
|
17
|
+
*/
|
|
18
|
+
export async function aggregateInsert(ctx, doc) {
|
|
19
|
+
await contactAggregate.insertIfDoesNotExist(ctx, doc);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Delete a contact from the aggregate
|
|
23
|
+
*/
|
|
24
|
+
export async function aggregateDelete(ctx, doc) {
|
|
25
|
+
await contactAggregate.deleteIfExists(ctx, doc);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Replace a contact in the aggregate (when userGroup changes)
|
|
29
|
+
*/
|
|
30
|
+
export async function aggregateReplace(ctx, oldDoc, newDoc) {
|
|
31
|
+
await contactAggregate.replaceOrInsert(ctx, oldDoc, newDoc);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Count contacts by userGroup namespace
|
|
35
|
+
*/
|
|
36
|
+
export async function aggregateCountByUserGroup(ctx, userGroup) {
|
|
37
|
+
return await contactAggregate.count(ctx, { namespace: userGroup });
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Count all contacts across all userGroups
|
|
41
|
+
*/
|
|
42
|
+
export async function aggregateCountTotal(ctx) {
|
|
43
|
+
let total = 0;
|
|
44
|
+
for await (const namespace of contactAggregate.iterNamespaces(ctx)) {
|
|
45
|
+
total += await contactAggregate.count(ctx, { namespace });
|
|
46
|
+
}
|
|
47
|
+
return total;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Clear and reinitialize the aggregate (for backfill)
|
|
51
|
+
*/
|
|
52
|
+
export async function aggregateClear(ctx, namespace) {
|
|
53
|
+
await contactAggregate.clear(ctx, { namespace });
|
|
54
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convex.config.d.ts","sourceRoot":"","sources":["../../src/component/convex.config.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,SAAS,kDAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"convex.config.d.ts","sourceRoot":"","sources":["../../src/component/convex.config.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,SAAS,kDAA2B,CAAC;AAI3C,eAAe,SAAS,CAAC"}
|
|
@@ -1,25 +1,5 @@
|
|
|
1
|
+
import aggregate from "@convex-dev/aggregate/convex.config";
|
|
1
2
|
import { defineComponent } from "convex/server";
|
|
2
|
-
import { api } from "./_generated/api";
|
|
3
3
|
const component = defineComponent("loops");
|
|
4
|
-
component.
|
|
5
|
-
addContact: api.lib.addContact,
|
|
6
|
-
updateContact: api.lib.updateContact,
|
|
7
|
-
findContact: api.lib.findContact,
|
|
8
|
-
batchCreateContacts: api.lib.batchCreateContacts,
|
|
9
|
-
unsubscribeContact: api.lib.unsubscribeContact,
|
|
10
|
-
resubscribeContact: api.lib.resubscribeContact,
|
|
11
|
-
countContacts: api.lib.countContacts,
|
|
12
|
-
listContacts: api.lib.listContacts,
|
|
13
|
-
sendTransactional: api.lib.sendTransactional,
|
|
14
|
-
sendEvent: api.lib.sendEvent,
|
|
15
|
-
triggerLoop: api.lib.triggerLoop,
|
|
16
|
-
deleteContact: api.lib.deleteContact,
|
|
17
|
-
detectRecipientSpam: api.lib.detectRecipientSpam,
|
|
18
|
-
detectActorSpam: api.lib.detectActorSpam,
|
|
19
|
-
getEmailStats: api.lib.getEmailStats,
|
|
20
|
-
detectRapidFirePatterns: api.lib.detectRapidFirePatterns,
|
|
21
|
-
checkRecipientRateLimit: api.lib.checkRecipientRateLimit,
|
|
22
|
-
checkActorRateLimit: api.lib.checkActorRateLimit,
|
|
23
|
-
checkGlobalRateLimit: api.lib.checkGlobalRateLimit,
|
|
24
|
-
});
|
|
4
|
+
component.use(aggregate, { name: "contactAggregate" });
|
|
25
5
|
export default component;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { HeadersInitParam } from "../types";
|
|
2
2
|
export declare const LOOPS_API_BASE_URL = "https://app.loops.so/api/v1";
|
|
3
3
|
export declare const sanitizeLoopsError: (status: number, _errorText: string) => Error;
|
|
4
4
|
export type LoopsRequestInit = Omit<RequestInit, "body"> & {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/component/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/component/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAWjD,eAAO,MAAM,kBAAkB,gCAAgC,CAAC;AAEhE,eAAO,MAAM,kBAAkB,WACtB,MAAM,cACF,MAAM,KAChB,KAcF,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,GAAG;IAC1D,IAAI,CAAC,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,eAAO,MAAM,UAAU,WACd,MAAM,QACR,MAAM,SACN,gBAAgB,sBAetB,CAAC;AAEF,eAAO,MAAM,gBAAgB,WAAY,gBAAgB,YAQxD,CAAC;AAEF,eAAO,MAAM,YAAY,SAAU,OAAO,SAAS,YAAY,aAM9D,CAAC;AAEF,eAAO,MAAM,aAAa,UAAW,YAAY,aAKhD,CAAC;AAEF,eAAO,MAAM,YAAY,GAAU,CAAC,WAAW,OAAO,KAAG,OAAO,CAAC,CAAC,CAMjE,CAAC;AAEF,eAAO,MAAM,gBAAgB,UAAW,MAAM,GAAG,IAAI,wBAWpD,CAAC;AAEF,eAAO,MAAM,eAAe,UAAW,MAAM,GAAG,IAAI,YAAY,MAAM,WAMrE,CAAC;AAEF,eAAO,MAAM,kBAAkB,cAQ9B,CAAC;AAEF,eAAO,MAAM,YAAY,UAAW,OAAO,aAS1C,CAAC"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import {} from "../types";
|
|
2
1
|
const allowedOrigin = process.env.CONVEX_URL ??
|
|
3
2
|
process.env.NEXT_PUBLIC_CONVEX_URL ??
|
|
4
3
|
process.env.CONVEX_SITE_URL ??
|
|
@@ -58,7 +57,7 @@ export const readJsonBody = async (request) => {
|
|
|
58
57
|
try {
|
|
59
58
|
return (await request.json());
|
|
60
59
|
}
|
|
61
|
-
catch (
|
|
60
|
+
catch (_error) {
|
|
62
61
|
throw new Error("Invalid JSON body");
|
|
63
62
|
}
|
|
64
63
|
};
|
package/dist/component/http.js
CHANGED
|
@@ -82,7 +82,7 @@ http.route({
|
|
|
82
82
|
source: url.searchParams.get("source") ?? undefined,
|
|
83
83
|
subscribed: booleanFromQuery(url.searchParams.get("subscribed")),
|
|
84
84
|
limit: numberFromQuery(url.searchParams.get("limit"), 100),
|
|
85
|
-
|
|
85
|
+
cursor: url.searchParams.get("cursor") ?? null,
|
|
86
86
|
});
|
|
87
87
|
return jsonResponse(data);
|
|
88
88
|
}
|
package/dist/component/lib.d.ts
CHANGED
|
@@ -36,9 +36,15 @@ export declare const logEmailOperation: import("convex/server").RegisteredMutati
|
|
|
36
36
|
* Count contacts in the database
|
|
37
37
|
* Can filter by audience criteria (userGroup, source, subscribed status)
|
|
38
38
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
39
|
+
* For userGroup-only filtering, uses efficient O(log n) aggregate counting.
|
|
40
|
+
* For other filters (source, subscribed), uses indexed queries with a read limit.
|
|
41
|
+
*
|
|
42
|
+
* IMPORTANT: Before using this with existing data, run the backfillContactAggregate
|
|
43
|
+
* mutation to populate the aggregate with existing contacts.
|
|
44
|
+
*
|
|
45
|
+
* NOTE: When filtering by source or subscribed, counts are capped at MAX_COUNT_LIMIT
|
|
46
|
+
* to avoid query read limit errors. For exact counts with large datasets, use
|
|
47
|
+
* userGroup-only filtering which uses efficient aggregate counting.
|
|
42
48
|
*/
|
|
43
49
|
export declare const countContacts: import("convex/server").RegisteredQuery<"public", {
|
|
44
50
|
userGroup?: string | undefined;
|
|
@@ -46,19 +52,22 @@ export declare const countContacts: import("convex/server").RegisteredQuery<"pub
|
|
|
46
52
|
subscribed?: boolean | undefined;
|
|
47
53
|
}, Promise<number>>;
|
|
48
54
|
/**
|
|
49
|
-
* List contacts from the database with pagination
|
|
55
|
+
* List contacts from the database with cursor-based pagination
|
|
50
56
|
* Can filter by audience criteria (userGroup, source, subscribed status)
|
|
51
57
|
* Returns actual contact data, not just a count
|
|
52
58
|
*
|
|
59
|
+
* Uses cursor-based pagination for efficient querying - only reads documents
|
|
60
|
+
* from the cursor position forward, not all preceding documents.
|
|
61
|
+
*
|
|
53
62
|
* Note: When multiple filters are provided, only one index can be used.
|
|
54
|
-
* Additional filters are applied in-memory
|
|
63
|
+
* Additional filters are applied in-memory after fetching.
|
|
55
64
|
*/
|
|
56
65
|
export declare const listContacts: import("convex/server").RegisteredQuery<"public", {
|
|
57
66
|
limit: number;
|
|
58
|
-
offset: number;
|
|
59
67
|
userGroup?: string | undefined;
|
|
60
68
|
source?: string | undefined;
|
|
61
69
|
subscribed?: boolean | undefined;
|
|
70
|
+
cursor?: string | null | undefined;
|
|
62
71
|
}, Promise<{
|
|
63
72
|
contacts: {
|
|
64
73
|
_id: string;
|
|
@@ -73,10 +82,8 @@ export declare const listContacts: import("convex/server").RegisteredQuery<"publ
|
|
|
73
82
|
userGroup?: string | undefined;
|
|
74
83
|
loopsContactId?: string | undefined;
|
|
75
84
|
}[];
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
offset: number;
|
|
79
|
-
hasMore: boolean;
|
|
85
|
+
continueCursor: string | null;
|
|
86
|
+
isDone: boolean;
|
|
80
87
|
}>>;
|
|
81
88
|
/**
|
|
82
89
|
* Add or update a contact in Loops
|
|
@@ -242,7 +249,10 @@ export declare const resubscribeContact: import("convex/server").RegisteredActio
|
|
|
242
249
|
}>>;
|
|
243
250
|
/**
|
|
244
251
|
* Check for spam patterns: too many emails to the same recipient in a time window
|
|
245
|
-
* Returns email addresses that received too many emails
|
|
252
|
+
* Returns email addresses that received too many emails.
|
|
253
|
+
*
|
|
254
|
+
* NOTE: Analysis is limited to the most recent MAX_SPAM_DETECTION_LIMIT operations
|
|
255
|
+
* in the time window to avoid query read limit errors.
|
|
246
256
|
*/
|
|
247
257
|
export declare const detectRecipientSpam: import("convex/server").RegisteredQuery<"public", {
|
|
248
258
|
timeWindowMs: number;
|
|
@@ -255,7 +265,10 @@ export declare const detectRecipientSpam: import("convex/server").RegisteredQuer
|
|
|
255
265
|
}[]>>;
|
|
256
266
|
/**
|
|
257
267
|
* Check for spam patterns: too many emails from the same actor/user
|
|
258
|
-
* Returns actor IDs that sent too many emails
|
|
268
|
+
* Returns actor IDs that sent too many emails.
|
|
269
|
+
*
|
|
270
|
+
* NOTE: Analysis is limited to the most recent MAX_SPAM_DETECTION_LIMIT operations
|
|
271
|
+
* in the time window to avoid query read limit errors.
|
|
259
272
|
*/
|
|
260
273
|
export declare const detectActorSpam: import("convex/server").RegisteredQuery<"public", {
|
|
261
274
|
timeWindowMs: number;
|
|
@@ -266,7 +279,11 @@ export declare const detectActorSpam: import("convex/server").RegisteredQuery<"p
|
|
|
266
279
|
timeWindowMs: number;
|
|
267
280
|
}[]>>;
|
|
268
281
|
/**
|
|
269
|
-
* Get recent email operation statistics for monitoring
|
|
282
|
+
* Get recent email operation statistics for monitoring.
|
|
283
|
+
*
|
|
284
|
+
* NOTE: Statistics are calculated from the most recent MAX_SPAM_DETECTION_LIMIT
|
|
285
|
+
* operations in the time window to avoid query read limit errors. For high-volume
|
|
286
|
+
* applications, consider using scheduled jobs with pagination for exact statistics.
|
|
270
287
|
*/
|
|
271
288
|
export declare const getEmailStats: import("convex/server").RegisteredQuery<"public", {
|
|
272
289
|
timeWindowMs: number;
|
|
@@ -281,7 +298,10 @@ export declare const getEmailStats: import("convex/server").RegisteredQuery<"pub
|
|
|
281
298
|
}>>;
|
|
282
299
|
/**
|
|
283
300
|
* Detect rapid-fire email sending patterns (multiple emails sent in quick succession)
|
|
284
|
-
* Returns suspicious patterns indicating potential spam
|
|
301
|
+
* Returns suspicious patterns indicating potential spam.
|
|
302
|
+
*
|
|
303
|
+
* NOTE: Analysis is limited to the most recent MAX_SPAM_DETECTION_LIMIT operations
|
|
304
|
+
* in the time window to avoid query read limit errors.
|
|
285
305
|
*/
|
|
286
306
|
export declare const detectRapidFirePatterns: import("convex/server").RegisteredQuery<"public", {
|
|
287
307
|
timeWindowMs: number;
|
|
@@ -296,7 +316,10 @@ export declare const detectRapidFirePatterns: import("convex/server").Registered
|
|
|
296
316
|
}[]>>;
|
|
297
317
|
/**
|
|
298
318
|
* Rate limiting: Check if an email can be sent to a recipient
|
|
299
|
-
* Based on recent email operations in the database
|
|
319
|
+
* Based on recent email operations in the database.
|
|
320
|
+
*
|
|
321
|
+
* Uses efficient .take() query - only reads the minimum number of documents
|
|
322
|
+
* needed to determine if the rate limit is exceeded.
|
|
300
323
|
*/
|
|
301
324
|
export declare const checkRecipientRateLimit: import("convex/server").RegisteredQuery<"public", {
|
|
302
325
|
email: string;
|
|
@@ -312,7 +335,10 @@ export declare const checkRecipientRateLimit: import("convex/server").Registered
|
|
|
312
335
|
}>>;
|
|
313
336
|
/**
|
|
314
337
|
* Rate limiting: Check if an actor/user can send more emails
|
|
315
|
-
* Based on recent email operations in the database
|
|
338
|
+
* Based on recent email operations in the database.
|
|
339
|
+
*
|
|
340
|
+
* Uses efficient .take() query - only reads the minimum number of documents
|
|
341
|
+
* needed to determine if the rate limit is exceeded.
|
|
316
342
|
*/
|
|
317
343
|
export declare const checkActorRateLimit: import("convex/server").RegisteredQuery<"public", {
|
|
318
344
|
actorId: string;
|
|
@@ -327,7 +353,10 @@ export declare const checkActorRateLimit: import("convex/server").RegisteredQuer
|
|
|
327
353
|
}>>;
|
|
328
354
|
/**
|
|
329
355
|
* Rate limiting: Check global email sending rate
|
|
330
|
-
* Checks total email operations across all senders
|
|
356
|
+
* Checks total email operations across all senders.
|
|
357
|
+
*
|
|
358
|
+
* Uses efficient .take() query - only reads the minimum number of documents
|
|
359
|
+
* needed to determine if the rate limit is exceeded.
|
|
331
360
|
*/
|
|
332
361
|
export declare const checkGlobalRateLimit: import("convex/server").RegisteredQuery<"public", {
|
|
333
362
|
timeWindowMs: number;
|
|
@@ -338,4 +367,24 @@ export declare const checkGlobalRateLimit: import("convex/server").RegisteredQue
|
|
|
338
367
|
limit: number;
|
|
339
368
|
timeWindowMs: number;
|
|
340
369
|
}>>;
|
|
370
|
+
/**
|
|
371
|
+
* Backfill the contact aggregate with existing contacts.
|
|
372
|
+
* Run this mutation after upgrading to a version with aggregate support.
|
|
373
|
+
*
|
|
374
|
+
* This processes contacts in batches to avoid timeout issues with large datasets.
|
|
375
|
+
* Call repeatedly with the returned cursor until isDone is true.
|
|
376
|
+
*
|
|
377
|
+
* Usage:
|
|
378
|
+
* 1. First call with clear: true to reset the aggregate
|
|
379
|
+
* 2. Subsequent calls with the returned cursor until isDone is true
|
|
380
|
+
*/
|
|
381
|
+
export declare const backfillContactAggregate: import("convex/server").RegisteredMutation<"public", {
|
|
382
|
+
batchSize: number;
|
|
383
|
+
cursor?: string | null | undefined;
|
|
384
|
+
clear?: boolean | undefined;
|
|
385
|
+
}, Promise<{
|
|
386
|
+
processed: number;
|
|
387
|
+
cursor: string | null;
|
|
388
|
+
isDone: boolean;
|
|
389
|
+
}>>;
|
|
341
390
|
//# sourceMappingURL=lib.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../../src/component/lib.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../../src/component/lib.ts"],"names":[],"mappings":"AAkBA;;GAEG;AACH,eAAO,MAAM,YAAY;;;;;;;;;iBA2DvB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa;;iBAiBxB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;iBAkC5B,CAAC;AAUH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa;;;;mBA8DxB,CAAC;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;GAgGvB,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;AASH;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;KA8C9B,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,eAAe;;;;;;;KA4C1B,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;GAkDxB,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;KAsGlC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;GAiDlC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;GA+C9B,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;GAiC/B,CAAC;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB;;;;;;;;GAsCnC,CAAC"}
|