@devwithbobby/loops 0.1.19 → 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 +47 -12
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +42 -4
- package/dist/component/_generated/api.d.ts +185 -1
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/component.d.ts +12 -5
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/_generated/dataModel.d.ts +1 -1
- 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 -0
- package/dist/component/helpers.d.ts.map +1 -1
- 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 +2 -2
- 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 +15 -9
- package/src/client/index.ts +52 -6
- package/src/component/_generated/api.ts +190 -1
- package/src/component/_generated/component.ts +10 -5
- package/src/component/_generated/dataModel.ts +1 -1
- package/src/component/aggregates.ts +89 -0
- package/src/component/convex.config.ts +3 -0
- package/src/component/http.ts +1 -1
- package/src/component/lib.ts +226 -89
- package/src/types.ts +20 -122
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import type * as aggregates from "../aggregates.js";
|
|
11
12
|
import type * as helpers from "../helpers.js";
|
|
12
13
|
import type * as http from "../http.js";
|
|
13
14
|
import type * as lib from "../lib.js";
|
|
@@ -23,6 +24,7 @@ import type {
|
|
|
23
24
|
import { anyApi, componentsGeneric } from "convex/server";
|
|
24
25
|
|
|
25
26
|
const fullApi: ApiFromModules<{
|
|
27
|
+
aggregates: typeof aggregates;
|
|
26
28
|
helpers: typeof helpers;
|
|
27
29
|
http: typeof http;
|
|
28
30
|
lib: typeof lib;
|
|
@@ -57,4 +59,191 @@ export const internal: FilterApi<
|
|
|
57
59
|
FunctionReference<any, "internal">
|
|
58
60
|
> = anyApi as any;
|
|
59
61
|
|
|
60
|
-
export const components = componentsGeneric() as unknown as {
|
|
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
|
+
};
|
|
@@ -42,6 +42,13 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
42
42
|
{ id?: string; success: boolean },
|
|
43
43
|
Name
|
|
44
44
|
>;
|
|
45
|
+
backfillContactAggregate: FunctionReference<
|
|
46
|
+
"mutation",
|
|
47
|
+
"internal",
|
|
48
|
+
{ batchSize?: number; clear?: boolean; cursor?: string | null },
|
|
49
|
+
{ cursor: string | null; isDone: boolean; processed: number },
|
|
50
|
+
Name
|
|
51
|
+
>;
|
|
45
52
|
batchCreateContacts: FunctionReference<
|
|
46
53
|
"action",
|
|
47
54
|
"internal",
|
|
@@ -183,8 +190,8 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
183
190
|
"query",
|
|
184
191
|
"internal",
|
|
185
192
|
{
|
|
193
|
+
cursor?: string | null;
|
|
186
194
|
limit?: number;
|
|
187
|
-
offset?: number;
|
|
188
195
|
source?: string;
|
|
189
196
|
subscribed?: boolean;
|
|
190
197
|
userGroup?: string;
|
|
@@ -203,10 +210,8 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
203
210
|
userGroup?: string;
|
|
204
211
|
userId?: string;
|
|
205
212
|
}>;
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
offset: number;
|
|
209
|
-
total: number;
|
|
213
|
+
continueCursor: string | null;
|
|
214
|
+
isDone: boolean;
|
|
210
215
|
},
|
|
211
216
|
Name
|
|
212
217
|
>;
|
|
@@ -38,7 +38,7 @@ export type Doc<TableName extends TableNames> = DocumentByName<
|
|
|
38
38
|
* Convex documents are uniquely identified by their `Id`, which is accessible
|
|
39
39
|
* on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids).
|
|
40
40
|
*
|
|
41
|
-
* Documents can be loaded using `db.get(
|
|
41
|
+
* Documents can be loaded using `db.get(id)` in query and mutation functions.
|
|
42
42
|
*
|
|
43
43
|
* IDs are just strings at runtime, but this type can be used to distinguish them from other
|
|
44
44
|
* strings when type checking.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { TableAggregate } from "@convex-dev/aggregate";
|
|
2
|
+
import type { GenericMutationCtx, GenericQueryCtx } from "convex/server";
|
|
3
|
+
import type { DataModel, Doc } from "./_generated/dataModel";
|
|
4
|
+
import { components } from "./_generated/api";
|
|
5
|
+
|
|
6
|
+
// Cast components to the expected type for the aggregate library
|
|
7
|
+
// biome-ignore lint/suspicious/noExplicitAny: Component API type mismatch with aggregate library
|
|
8
|
+
const contactAggregateComponent = components.contactAggregate as any;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Aggregate for counting contacts.
|
|
12
|
+
* Uses userGroup as namespace for efficient filtered counting.
|
|
13
|
+
* Key is null since we only need counts, not ordering.
|
|
14
|
+
*/
|
|
15
|
+
export const contactAggregate = new TableAggregate<{
|
|
16
|
+
Namespace: string | undefined;
|
|
17
|
+
Key: null;
|
|
18
|
+
DataModel: DataModel;
|
|
19
|
+
TableName: "contacts";
|
|
20
|
+
}>(contactAggregateComponent, {
|
|
21
|
+
namespace: (doc) => doc.userGroup,
|
|
22
|
+
sortKey: () => null,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
type MutationCtx = GenericMutationCtx<DataModel>;
|
|
26
|
+
type QueryCtx = GenericQueryCtx<DataModel>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Insert a contact into the aggregate
|
|
30
|
+
*/
|
|
31
|
+
export async function aggregateInsert(
|
|
32
|
+
ctx: MutationCtx,
|
|
33
|
+
doc: Doc<"contacts">,
|
|
34
|
+
): Promise<void> {
|
|
35
|
+
await contactAggregate.insertIfDoesNotExist(ctx, doc);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Delete a contact from the aggregate
|
|
40
|
+
*/
|
|
41
|
+
export async function aggregateDelete(
|
|
42
|
+
ctx: MutationCtx,
|
|
43
|
+
doc: Doc<"contacts">,
|
|
44
|
+
): Promise<void> {
|
|
45
|
+
await contactAggregate.deleteIfExists(ctx, doc);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Replace a contact in the aggregate (when userGroup changes)
|
|
50
|
+
*/
|
|
51
|
+
export async function aggregateReplace(
|
|
52
|
+
ctx: MutationCtx,
|
|
53
|
+
oldDoc: Doc<"contacts">,
|
|
54
|
+
newDoc: Doc<"contacts">,
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
await contactAggregate.replaceOrInsert(ctx, oldDoc, newDoc);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Count contacts by userGroup namespace
|
|
61
|
+
*/
|
|
62
|
+
export async function aggregateCountByUserGroup(
|
|
63
|
+
ctx: QueryCtx,
|
|
64
|
+
userGroup: string | undefined,
|
|
65
|
+
): Promise<number> {
|
|
66
|
+
return await contactAggregate.count(ctx, { namespace: userGroup });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Count all contacts across all userGroups
|
|
71
|
+
*/
|
|
72
|
+
export async function aggregateCountTotal(ctx: QueryCtx): Promise<number> {
|
|
73
|
+
let total = 0;
|
|
74
|
+
for await (const namespace of contactAggregate.iterNamespaces(ctx)) {
|
|
75
|
+
total += await contactAggregate.count(ctx, { namespace });
|
|
76
|
+
}
|
|
77
|
+
return total;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Clear and reinitialize the aggregate (for backfill)
|
|
82
|
+
*/
|
|
83
|
+
export async function aggregateClear(
|
|
84
|
+
ctx: MutationCtx,
|
|
85
|
+
namespace?: string,
|
|
86
|
+
): Promise<void> {
|
|
87
|
+
await contactAggregate.clear(ctx, { namespace });
|
|
88
|
+
}
|
|
89
|
+
|
package/src/component/http.ts
CHANGED
|
@@ -107,7 +107,7 @@ http.route({
|
|
|
107
107
|
source: url.searchParams.get("source") ?? undefined,
|
|
108
108
|
subscribed: booleanFromQuery(url.searchParams.get("subscribed")),
|
|
109
109
|
limit: numberFromQuery(url.searchParams.get("limit"), 100),
|
|
110
|
-
|
|
110
|
+
cursor: url.searchParams.get("cursor") ?? null,
|
|
111
111
|
});
|
|
112
112
|
return jsonResponse(data);
|
|
113
113
|
} catch (error) {
|