@devwithbobby/loops 0.1.18 → 0.1.19

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 (33) hide show
  1. package/dist/client/index.d.ts +91 -96
  2. package/dist/client/index.d.ts.map +1 -1
  3. package/dist/client/index.js +13 -6
  4. package/dist/component/_generated/api.d.ts +44 -0
  5. package/dist/component/_generated/api.d.ts.map +1 -0
  6. package/{src → dist}/component/_generated/api.js +10 -3
  7. package/dist/component/_generated/component.d.ts +259 -0
  8. package/dist/component/_generated/component.d.ts.map +1 -0
  9. package/dist/component/_generated/component.js +9 -0
  10. package/dist/component/_generated/dataModel.d.ts +46 -0
  11. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  12. package/dist/component/_generated/dataModel.js +10 -0
  13. package/{src → dist}/component/_generated/server.d.ts +10 -38
  14. package/dist/component/_generated/server.d.ts.map +1 -0
  15. package/{src → dist}/component/_generated/server.js +9 -22
  16. package/dist/component/convex.config.d.ts.map +1 -1
  17. package/dist/component/convex.config.js +0 -22
  18. package/dist/component/helpers.d.ts +1 -1
  19. package/dist/component/helpers.d.ts.map +1 -1
  20. package/dist/component/helpers.js +1 -2
  21. package/dist/test.d.ts +83 -0
  22. package/dist/test.d.ts.map +1 -0
  23. package/dist/test.js +16 -0
  24. package/package.json +15 -9
  25. package/src/client/index.ts +18 -13
  26. package/src/component/_generated/api.ts +60 -0
  27. package/src/component/_generated/component.ts +323 -0
  28. package/src/component/_generated/{dataModel.d.ts → dataModel.ts} +1 -1
  29. package/src/component/_generated/server.ts +161 -0
  30. package/src/component/convex.config.ts +0 -27
  31. package/src/component/helpers.ts +2 -2
  32. package/src/test.ts +27 -0
  33. package/src/component/_generated/api.d.ts +0 -47
package/dist/test.d.ts ADDED
@@ -0,0 +1,83 @@
1
+ import type { TestConvex } from "convex-test";
2
+ import type { GenericSchema, SchemaDefinition } from "convex/server";
3
+ import schema from "./component/schema.js";
4
+ /**
5
+ * Register the Loops component with the test convex instance.
6
+ *
7
+ * @param t - The test convex instance from convexTest().
8
+ * @param name - The name of the component as registered in convex.config.ts.
9
+ * @param modules - The modules object from import.meta.glob. Required.
10
+ */
11
+ export declare function register(t: TestConvex<SchemaDefinition<GenericSchema, boolean>>, name?: string, modules?: Record<string, () => Promise<unknown>>): void;
12
+ export { schema };
13
+ declare const _default: {
14
+ register: typeof register;
15
+ schema: SchemaDefinition<{
16
+ contacts: import("convex/server").TableDefinition<import("convex/values").VObject<{
17
+ firstName?: string | undefined;
18
+ lastName?: string | undefined;
19
+ userId?: string | undefined;
20
+ source?: string | undefined;
21
+ subscribed?: boolean | undefined;
22
+ userGroup?: string | undefined;
23
+ loopsContactId?: string | undefined;
24
+ email: string;
25
+ createdAt: number;
26
+ updatedAt: number;
27
+ }, import("zodvex").ConvexValidatorFromZodFieldsAuto<{
28
+ email: import("zod").ZodString;
29
+ firstName: import("zod").ZodOptional<import("zod").ZodString>;
30
+ lastName: import("zod").ZodOptional<import("zod").ZodString>;
31
+ userId: import("zod").ZodOptional<import("zod").ZodString>;
32
+ source: import("zod").ZodOptional<import("zod").ZodString>;
33
+ subscribed: import("zod").ZodDefault<import("zod").ZodBoolean>;
34
+ userGroup: import("zod").ZodOptional<import("zod").ZodString>;
35
+ loopsContactId: import("zod").ZodOptional<import("zod").ZodString>;
36
+ createdAt: import("zod").ZodNumber;
37
+ updatedAt: import("zod").ZodNumber;
38
+ }>, "required", "email" | "firstName" | "lastName" | "userId" | "source" | "subscribed" | "userGroup" | "loopsContactId" | "createdAt" | "updatedAt">, {
39
+ email: ["email", "_creationTime"];
40
+ userId: ["userId", "_creationTime"];
41
+ userGroup: ["userGroup", "_creationTime"];
42
+ source: ["source", "_creationTime"];
43
+ subscribed: ["subscribed", "_creationTime"];
44
+ }, {}, {}>;
45
+ emailOperations: import("convex/server").TableDefinition<import("convex/values").VObject<{
46
+ metadata?: Record<string, any> | undefined;
47
+ actorId?: string | undefined;
48
+ transactionalId?: string | undefined;
49
+ campaignId?: string | undefined;
50
+ loopId?: string | undefined;
51
+ eventName?: string | undefined;
52
+ messageId?: string | undefined;
53
+ email: string;
54
+ success: boolean;
55
+ operationType: "transactional" | "event" | "campaign" | "loop";
56
+ timestamp: number;
57
+ }, import("zodvex").ConvexValidatorFromZodFieldsAuto<{
58
+ operationType: import("zod").ZodEnum<{
59
+ transactional: "transactional";
60
+ event: "event";
61
+ campaign: "campaign";
62
+ loop: "loop";
63
+ }>;
64
+ email: import("zod").ZodString;
65
+ actorId: import("zod").ZodOptional<import("zod").ZodString>;
66
+ transactionalId: import("zod").ZodOptional<import("zod").ZodString>;
67
+ campaignId: import("zod").ZodOptional<import("zod").ZodString>;
68
+ loopId: import("zod").ZodOptional<import("zod").ZodString>;
69
+ eventName: import("zod").ZodOptional<import("zod").ZodString>;
70
+ timestamp: import("zod").ZodNumber;
71
+ success: import("zod").ZodBoolean;
72
+ messageId: import("zod").ZodOptional<import("zod").ZodString>;
73
+ metadata: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
74
+ }>, "required", "email" | "success" | "metadata" | "operationType" | "actorId" | "transactionalId" | "campaignId" | "loopId" | "eventName" | "timestamp" | "messageId" | `metadata.${string}`>, {
75
+ email: ["email", "timestamp", "_creationTime"];
76
+ actorId: ["actorId", "timestamp", "_creationTime"];
77
+ operationType: ["operationType", "timestamp", "_creationTime"];
78
+ timestamp: ["timestamp", "_creationTime"];
79
+ }, {}, {}>;
80
+ }, true>;
81
+ };
82
+ export default _default;
83
+ //# sourceMappingURL=test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,MAAM,MAAM,uBAAuB,CAAC;AAE3C;;;;;;GAMG;AACH,wBAAgB,QAAQ,CACvB,CAAC,EAAE,UAAU,CAAC,gBAAgB,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,EACvD,IAAI,GAAE,MAAgB,EACtB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC,QAQhD;AAED,OAAO,EAAE,MAAM,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAElB,wBAAoC"}
package/dist/test.js ADDED
@@ -0,0 +1,16 @@
1
+ import schema from "./component/schema.js";
2
+ /**
3
+ * Register the Loops component with the test convex instance.
4
+ *
5
+ * @param t - The test convex instance from convexTest().
6
+ * @param name - The name of the component as registered in convex.config.ts.
7
+ * @param modules - The modules object from import.meta.glob. Required.
8
+ */
9
+ export function register(t, name = "loops", modules) {
10
+ if (!modules) {
11
+ throw new Error("modules parameter is required. Pass import.meta.glob from your test file.");
12
+ }
13
+ t.registerComponent(name, schema, modules);
14
+ }
15
+ export { schema };
16
+ export default { register, schema };
package/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "@devwithbobby/loops",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "Convex component for integrating with Loops.so email marketing platform",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "keywords": [
8
8
  "convex",
9
9
  "component",
10
- "template",
11
- "boilerplate",
12
- "bun",
13
- "biome",
10
+ "loops",
11
+ "loops.so",
12
+ "email",
13
+ "email-marketing",
14
+ "transactional-email",
15
+ "newsletter",
14
16
  "typescript"
15
17
  ],
16
18
  "repository": {
@@ -38,7 +40,13 @@
38
40
  "@convex-dev/component-source": "./src/component/convex.config.ts",
39
41
  "types": "./dist/component/convex.config.d.ts",
40
42
  "default": "./src/component/convex.config.ts"
41
- }
43
+ },
44
+ "./component": {
45
+ "@convex-dev/component-source": "./src/component/_generated/component.ts",
46
+ "types": "./dist/component/_generated/component.d.ts",
47
+ "default": "./dist/component/_generated/component.js"
48
+ },
49
+ "./test": "./src/test.ts"
42
50
  },
43
51
  "scripts": {
44
52
  "dev": "run-p -r 'dev:backend' 'dev:frontend' 'build:watch'",
@@ -73,9 +81,7 @@
73
81
  "zodvex": "^0.2.3"
74
82
  },
75
83
  "peerDependencies": {
76
- "convex": "^1.0.0",
77
- "react": "^18.0.0 || ^19.0.0",
78
- "typescript": "^5.9.3"
84
+ "convex": "^1.0.0"
79
85
  },
80
86
  "devDependencies": {
81
87
  "@arethetypeswrong/cli": "^0.18.2",
@@ -1,9 +1,9 @@
1
1
  import { actionGeneric, queryGeneric } from "convex/server";
2
2
  import { v } from "convex/values";
3
- import type { Mounts } from "../component/_generated/api";
4
- import type { RunActionCtx, RunQueryCtx, UseApi } from "../types";
3
+ import type { ComponentApi } from "../component/_generated/component.js";
4
+ import type { RunActionCtx, RunQueryCtx } from "../types";
5
5
 
6
- export type LoopsComponent = UseApi<Mounts>;
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
- private readonly apiKey: string;
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
@@ -0,0 +1,60 @@
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 helpers from "../helpers.js";
12
+ import type * as http from "../http.js";
13
+ import type * as lib from "../lib.js";
14
+ import type * as tables_contacts from "../tables/contacts.js";
15
+ import type * as tables_emailOperations from "../tables/emailOperations.js";
16
+ import type * as validators from "../validators.js";
17
+
18
+ import type {
19
+ ApiFromModules,
20
+ FilterApi,
21
+ FunctionReference,
22
+ } from "convex/server";
23
+ import { anyApi, componentsGeneric } from "convex/server";
24
+
25
+ const fullApi: ApiFromModules<{
26
+ helpers: typeof helpers;
27
+ http: typeof http;
28
+ lib: typeof lib;
29
+ "tables/contacts": typeof tables_contacts;
30
+ "tables/emailOperations": typeof tables_emailOperations;
31
+ validators: typeof validators;
32
+ }> = anyApi as any;
33
+
34
+ /**
35
+ * A utility for referencing Convex functions in your app's public API.
36
+ *
37
+ * Usage:
38
+ * ```js
39
+ * const myFunctionReference = api.myModule.myFunction;
40
+ * ```
41
+ */
42
+ export const api: FilterApi<
43
+ typeof fullApi,
44
+ FunctionReference<any, "public">
45
+ > = anyApi as any;
46
+
47
+ /**
48
+ * A utility for referencing Convex functions in your app's internal API.
49
+ *
50
+ * Usage:
51
+ * ```js
52
+ * const myFunctionReference = internal.myModule.myFunction;
53
+ * ```
54
+ */
55
+ export const internal: FilterApi<
56
+ typeof fullApi,
57
+ FunctionReference<any, "internal">
58
+ > = anyApi as any;
59
+
60
+ export const components = componentsGeneric() as unknown as {};
@@ -0,0 +1,323 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * Generated `ComponentApi` utility.
4
+ *
5
+ * THIS CODE IS AUTOMATICALLY GENERATED.
6
+ *
7
+ * To regenerate, run `npx convex dev`.
8
+ * @module
9
+ */
10
+
11
+ import type { FunctionReference } from "convex/server";
12
+
13
+ /**
14
+ * A utility for referencing a Convex component's exposed API.
15
+ *
16
+ * Useful when expecting a parameter like `components.myComponent`.
17
+ * Usage:
18
+ * ```ts
19
+ * async function myFunction(ctx: QueryCtx, component: ComponentApi) {
20
+ * return ctx.runQuery(component.someFile.someQuery, { ...args });
21
+ * }
22
+ * ```
23
+ */
24
+ export type ComponentApi<Name extends string | undefined = string | undefined> =
25
+ {
26
+ lib: {
27
+ addContact: FunctionReference<
28
+ "action",
29
+ "internal",
30
+ {
31
+ apiKey: string;
32
+ contact: {
33
+ email: string;
34
+ firstName?: string;
35
+ lastName?: string;
36
+ source?: string;
37
+ subscribed?: boolean;
38
+ userGroup?: string;
39
+ userId?: string;
40
+ };
41
+ },
42
+ { id?: string; success: boolean },
43
+ Name
44
+ >;
45
+ batchCreateContacts: FunctionReference<
46
+ "action",
47
+ "internal",
48
+ {
49
+ apiKey: string;
50
+ contacts: Array<{
51
+ email: string;
52
+ firstName?: string;
53
+ lastName?: string;
54
+ source?: string;
55
+ subscribed?: boolean;
56
+ userGroup?: string;
57
+ userId?: string;
58
+ }>;
59
+ },
60
+ {
61
+ created?: number;
62
+ failed?: number;
63
+ results?: Array<{ email: string; error?: string; success: boolean }>;
64
+ success: boolean;
65
+ },
66
+ Name
67
+ >;
68
+ checkActorRateLimit: FunctionReference<
69
+ "query",
70
+ "internal",
71
+ { actorId: string; maxEmails: number; timeWindowMs: number },
72
+ {
73
+ allowed: boolean;
74
+ count: number;
75
+ limit: number;
76
+ retryAfter?: number;
77
+ timeWindowMs: number;
78
+ },
79
+ Name
80
+ >;
81
+ checkGlobalRateLimit: FunctionReference<
82
+ "query",
83
+ "internal",
84
+ { maxEmails: number; timeWindowMs: number },
85
+ {
86
+ allowed: boolean;
87
+ count: number;
88
+ limit: number;
89
+ timeWindowMs: number;
90
+ },
91
+ Name
92
+ >;
93
+ checkRecipientRateLimit: FunctionReference<
94
+ "query",
95
+ "internal",
96
+ { email: string; maxEmails: number; timeWindowMs: number },
97
+ {
98
+ allowed: boolean;
99
+ count: number;
100
+ limit: number;
101
+ retryAfter?: number;
102
+ timeWindowMs: number;
103
+ },
104
+ Name
105
+ >;
106
+ countContacts: FunctionReference<
107
+ "query",
108
+ "internal",
109
+ { source?: string; subscribed?: boolean; userGroup?: string },
110
+ number,
111
+ Name
112
+ >;
113
+ deleteContact: FunctionReference<
114
+ "action",
115
+ "internal",
116
+ { apiKey: string; email: string },
117
+ { success: boolean },
118
+ Name
119
+ >;
120
+ detectActorSpam: FunctionReference<
121
+ "query",
122
+ "internal",
123
+ { maxEmailsPerActor?: number; timeWindowMs?: number },
124
+ Array<{ actorId: string; count: number; timeWindowMs: number }>,
125
+ Name
126
+ >;
127
+ detectRapidFirePatterns: FunctionReference<
128
+ "query",
129
+ "internal",
130
+ { minEmailsInWindow?: number; timeWindowMs?: number },
131
+ Array<{
132
+ actorId?: string;
133
+ count: number;
134
+ email?: string;
135
+ firstTimestamp: number;
136
+ lastTimestamp: number;
137
+ timeWindowMs: number;
138
+ }>,
139
+ Name
140
+ >;
141
+ detectRecipientSpam: FunctionReference<
142
+ "query",
143
+ "internal",
144
+ { maxEmailsPerRecipient?: number; timeWindowMs?: number },
145
+ Array<{ count: number; email: string; timeWindowMs: number }>,
146
+ Name
147
+ >;
148
+ findContact: FunctionReference<
149
+ "action",
150
+ "internal",
151
+ { apiKey: string; email: string },
152
+ {
153
+ contact?: {
154
+ createdAt?: string | null;
155
+ email?: string | null;
156
+ firstName?: string | null;
157
+ id?: string | null;
158
+ lastName?: string | null;
159
+ source?: string | null;
160
+ subscribed?: boolean | null;
161
+ userGroup?: string | null;
162
+ userId?: string | null;
163
+ };
164
+ success: boolean;
165
+ },
166
+ Name
167
+ >;
168
+ getEmailStats: FunctionReference<
169
+ "query",
170
+ "internal",
171
+ { timeWindowMs?: number },
172
+ {
173
+ failedOperations: number;
174
+ operationsByType: Record<string, number>;
175
+ successfulOperations: number;
176
+ totalOperations: number;
177
+ uniqueActors: number;
178
+ uniqueRecipients: number;
179
+ },
180
+ Name
181
+ >;
182
+ listContacts: FunctionReference<
183
+ "query",
184
+ "internal",
185
+ {
186
+ limit?: number;
187
+ offset?: number;
188
+ source?: string;
189
+ subscribed?: boolean;
190
+ userGroup?: string;
191
+ },
192
+ {
193
+ contacts: Array<{
194
+ _id: string;
195
+ createdAt: number;
196
+ email: string;
197
+ firstName?: string;
198
+ lastName?: string;
199
+ loopsContactId?: string;
200
+ source?: string;
201
+ subscribed: boolean;
202
+ updatedAt: number;
203
+ userGroup?: string;
204
+ userId?: string;
205
+ }>;
206
+ hasMore: boolean;
207
+ limit: number;
208
+ offset: number;
209
+ total: number;
210
+ },
211
+ Name
212
+ >;
213
+ logEmailOperation: FunctionReference<
214
+ "mutation",
215
+ "internal",
216
+ {
217
+ actorId?: string;
218
+ campaignId?: string;
219
+ email: string;
220
+ eventName?: string;
221
+ loopId?: string;
222
+ messageId?: string;
223
+ metadata?: Record<string, any>;
224
+ operationType: "transactional" | "event" | "campaign" | "loop";
225
+ success: boolean;
226
+ transactionalId?: string;
227
+ },
228
+ any,
229
+ Name
230
+ >;
231
+ removeContact: FunctionReference<
232
+ "mutation",
233
+ "internal",
234
+ { email: string },
235
+ any,
236
+ Name
237
+ >;
238
+ resubscribeContact: FunctionReference<
239
+ "action",
240
+ "internal",
241
+ { apiKey: string; email: string },
242
+ { success: boolean },
243
+ Name
244
+ >;
245
+ sendEvent: FunctionReference<
246
+ "action",
247
+ "internal",
248
+ {
249
+ apiKey: string;
250
+ email: string;
251
+ eventName: string;
252
+ eventProperties?: Record<string, any>;
253
+ },
254
+ { success: boolean },
255
+ Name
256
+ >;
257
+ sendTransactional: FunctionReference<
258
+ "action",
259
+ "internal",
260
+ {
261
+ apiKey: string;
262
+ dataVariables?: Record<string, any>;
263
+ email: string;
264
+ transactionalId: string;
265
+ },
266
+ { messageId?: string; success: boolean },
267
+ Name
268
+ >;
269
+ storeContact: FunctionReference<
270
+ "mutation",
271
+ "internal",
272
+ {
273
+ email: string;
274
+ firstName?: string;
275
+ lastName?: string;
276
+ loopsContactId?: string;
277
+ source?: string;
278
+ subscribed?: boolean;
279
+ userGroup?: string;
280
+ userId?: string;
281
+ },
282
+ any,
283
+ Name
284
+ >;
285
+ triggerLoop: FunctionReference<
286
+ "action",
287
+ "internal",
288
+ {
289
+ apiKey: string;
290
+ dataVariables?: Record<string, any>;
291
+ email: string;
292
+ eventName?: string;
293
+ loopId: string;
294
+ },
295
+ { success: boolean; warning?: string },
296
+ Name
297
+ >;
298
+ unsubscribeContact: FunctionReference<
299
+ "action",
300
+ "internal",
301
+ { apiKey: string; email: string },
302
+ { success: boolean },
303
+ Name
304
+ >;
305
+ updateContact: FunctionReference<
306
+ "action",
307
+ "internal",
308
+ {
309
+ apiKey: string;
310
+ dataVariables?: Record<string, any>;
311
+ email: string;
312
+ firstName?: string;
313
+ lastName?: string;
314
+ source?: string;
315
+ subscribed?: boolean;
316
+ userGroup?: string;
317
+ userId?: string;
318
+ },
319
+ { success: boolean },
320
+ Name
321
+ >;
322
+ };
323
+ };
@@ -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(id)` in query and mutation functions.
41
+ * Documents can be loaded using `db.get(tableName, 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.