@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.
Files changed (37) hide show
  1. package/dist/client/index.d.ts +47 -12
  2. package/dist/client/index.d.ts.map +1 -1
  3. package/dist/client/index.js +42 -4
  4. package/dist/component/_generated/api.d.ts +185 -1
  5. package/dist/component/_generated/api.d.ts.map +1 -1
  6. package/dist/component/_generated/component.d.ts +12 -5
  7. package/dist/component/_generated/component.d.ts.map +1 -1
  8. package/dist/component/_generated/dataModel.d.ts +1 -1
  9. package/dist/component/aggregates.d.ts +42 -0
  10. package/dist/component/aggregates.d.ts.map +1 -0
  11. package/dist/component/aggregates.js +54 -0
  12. package/dist/component/convex.config.d.ts.map +1 -1
  13. package/dist/component/convex.config.js +2 -0
  14. package/dist/component/helpers.d.ts.map +1 -1
  15. package/dist/component/http.js +1 -1
  16. package/dist/component/lib.d.ts +66 -17
  17. package/dist/component/lib.d.ts.map +1 -1
  18. package/dist/component/lib.js +194 -73
  19. package/dist/component/schema.d.ts +2 -2
  20. package/dist/component/tables/contacts.d.ts.map +1 -1
  21. package/dist/component/tables/emailOperations.d.ts +4 -4
  22. package/dist/component/tables/emailOperations.d.ts.map +1 -1
  23. package/dist/test.d.ts +2 -2
  24. package/dist/types.d.ts +249 -62
  25. package/dist/types.d.ts.map +1 -1
  26. package/dist/types.js +4 -2
  27. package/dist/utils.d.ts +6 -6
  28. package/package.json +15 -9
  29. package/src/client/index.ts +52 -6
  30. package/src/component/_generated/api.ts +190 -1
  31. package/src/component/_generated/component.ts +10 -5
  32. package/src/component/_generated/dataModel.ts +1 -1
  33. package/src/component/aggregates.ts +89 -0
  34. package/src/component/convex.config.ts +3 -0
  35. package/src/component/http.ts +1 -1
  36. package/src/component/lib.ts +226 -89
  37. 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
- hasMore: boolean;
207
- limit: number;
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(tableName, id)` in query and mutation functions.
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
+
@@ -1,5 +1,8 @@
1
+ import aggregate from "@convex-dev/aggregate/convex.config";
1
2
  import { defineComponent } from "convex/server";
2
3
 
3
4
  const component = defineComponent("loops");
4
5
 
6
+ component.use(aggregate, { name: "contactAggregate" });
7
+
5
8
  export default component;
@@ -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
- offset: numberFromQuery(url.searchParams.get("offset"), 0),
110
+ cursor: url.searchParams.get("cursor") ?? null,
111
111
  });
112
112
  return jsonResponse(data);
113
113
  } catch (error) {