@devwithbobby/loops 0.1.12 → 0.1.14
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 +305 -44
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +21 -32
- package/dist/component/convex.config.d.ts +1 -1
- package/dist/component/convex.config.d.ts.map +1 -1
- package/dist/component/convex.config.js +1 -1
- package/dist/component/helpers.d.ts +7 -0
- package/dist/component/helpers.d.ts.map +1 -0
- package/dist/component/helpers.js +30 -0
- package/dist/component/http.d.ts +3 -0
- package/dist/component/http.d.ts.map +1 -0
- package/dist/component/http.js +268 -0
- package/dist/component/lib.d.ts +237 -22
- package/dist/component/lib.d.ts.map +1 -1
- package/dist/component/lib.js +91 -143
- package/dist/component/schema.d.ts +66 -1
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/tables/contacts.d.ts +123 -1
- package/dist/component/tables/contacts.d.ts.map +1 -1
- package/dist/component/tables/emailOperations.d.ts +151 -1
- package/dist/component/tables/emailOperations.d.ts.map +1 -1
- package/dist/component/tables/emailOperations.js +1 -6
- package/dist/component/validators.d.ts +20 -3
- package/dist/component/validators.d.ts.map +1 -1
- package/dist/types.d.ts +97 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/utils.d.ts +186 -3
- package/dist/utils.d.ts.map +1 -1
- package/package.json +101 -101
- package/src/client/index.ts +40 -52
- package/src/component/_generated/api.d.ts +3 -11
- package/src/component/convex.config.ts +7 -2
- package/src/component/helpers.ts +44 -0
- package/src/component/http.ts +304 -0
- package/src/component/lib.ts +189 -204
- package/src/component/tables/contacts.ts +0 -1
- package/src/component/tables/emailOperations.ts +1 -7
- package/src/component/validators.ts +0 -1
- package/src/types.ts +168 -0
- package/src/client/types.ts +0 -64
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convex.config.d.ts","sourceRoot":"","sources":["../../src/component/convex.config.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"convex.config.d.ts","sourceRoot":"","sources":["../../src/component/convex.config.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,SAAS,kDAA2B,CAAC;AA4B3C,eAAe,SAAS,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const LOOPS_API_BASE_URL = "https://app.loops.so/api/v1";
|
|
2
|
+
export declare const sanitizeLoopsError: (status: number, _errorText: string) => Error;
|
|
3
|
+
export type LoopsRequestInit = Omit<RequestInit, "body"> & {
|
|
4
|
+
json?: unknown;
|
|
5
|
+
};
|
|
6
|
+
export declare const loopsFetch: (apiKey: string, path: string, init?: LoopsRequestInit) => Promise<Response>;
|
|
7
|
+
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/component/helpers.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,gCAAgC,CAAC;AAEhE,eAAO,MAAM,kBAAkB,GAC9B,QAAQ,MAAM,EACd,YAAY,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,GACtB,QAAQ,MAAM,EACd,MAAM,MAAM,EACZ,OAAM,gBAAqB,sBAe3B,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const LOOPS_API_BASE_URL = "https://app.loops.so/api/v1";
|
|
2
|
+
export const sanitizeLoopsError = (status, _errorText) => {
|
|
3
|
+
if (status === 401 || status === 403) {
|
|
4
|
+
return new Error("Authentication failed. Please check your API key.");
|
|
5
|
+
}
|
|
6
|
+
if (status === 404) {
|
|
7
|
+
return new Error("Resource not found.");
|
|
8
|
+
}
|
|
9
|
+
if (status === 429) {
|
|
10
|
+
return new Error("Rate limit exceeded. Please try again later.");
|
|
11
|
+
}
|
|
12
|
+
if (status >= 500) {
|
|
13
|
+
return new Error("Loops service error. Please try again later.");
|
|
14
|
+
}
|
|
15
|
+
return new Error(`Loops API error (${status}). Please try again.`);
|
|
16
|
+
};
|
|
17
|
+
export const loopsFetch = async (apiKey, path, init = {}) => {
|
|
18
|
+
const { json, ...rest } = init;
|
|
19
|
+
const headers = new Headers(rest.headers ?? {});
|
|
20
|
+
headers.set("Authorization", `Bearer ${apiKey}`);
|
|
21
|
+
if (json !== undefined && !headers.has("Content-Type")) {
|
|
22
|
+
headers.set("Content-Type", "application/json");
|
|
23
|
+
}
|
|
24
|
+
return fetch(`${LOOPS_API_BASE_URL}${path}`, {
|
|
25
|
+
...rest,
|
|
26
|
+
headers,
|
|
27
|
+
// @ts-expect-error RequestInit in this build doesn't declare body
|
|
28
|
+
body: json !== undefined ? JSON.stringify(json) : rest.body,
|
|
29
|
+
});
|
|
30
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/component/http.ts"],"names":[],"mappings":"AAaA,QAAA,MAAM,IAAI,oCAAe,CAAC;AAkS1B,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { httpRouter } from "convex/server";
|
|
2
|
+
import { internalLib, } from "../types";
|
|
3
|
+
import { httpAction } from "./_generated/server";
|
|
4
|
+
const http = httpRouter();
|
|
5
|
+
const allowedOrigin = process.env.LOOPS_HTTP_ALLOWED_ORIGIN ?? process.env.CLIENT_ORIGIN ?? "*";
|
|
6
|
+
const buildCorsHeaders = (extra) => {
|
|
7
|
+
const headers = new Headers(extra ?? {});
|
|
8
|
+
headers.set("Access-Control-Allow-Origin", allowedOrigin);
|
|
9
|
+
headers.set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
|
|
10
|
+
headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
11
|
+
headers.set("Access-Control-Max-Age", "86400");
|
|
12
|
+
headers.set("Vary", "Origin");
|
|
13
|
+
return headers;
|
|
14
|
+
};
|
|
15
|
+
const jsonResponse = (data, init) => {
|
|
16
|
+
const headers = buildCorsHeaders(init?.headers ?? undefined);
|
|
17
|
+
headers.set("Content-Type", "application/json");
|
|
18
|
+
return new Response(JSON.stringify(data), { ...init, headers });
|
|
19
|
+
};
|
|
20
|
+
const emptyResponse = (init) => {
|
|
21
|
+
const headers = buildCorsHeaders(init?.headers ?? undefined);
|
|
22
|
+
return new Response(null, { ...init, headers });
|
|
23
|
+
};
|
|
24
|
+
const readJsonBody = async (request) => {
|
|
25
|
+
try {
|
|
26
|
+
return (await request.json());
|
|
27
|
+
}
|
|
28
|
+
catch (_error) {
|
|
29
|
+
throw new Error("Invalid JSON body");
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const booleanFromQuery = (value) => {
|
|
33
|
+
if (value === null) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
if (value === "true") {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (value === "false") {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
};
|
|
44
|
+
const numberFromQuery = (value, fallback) => {
|
|
45
|
+
if (!value) {
|
|
46
|
+
return fallback;
|
|
47
|
+
}
|
|
48
|
+
const parsed = Number.parseInt(value, 10);
|
|
49
|
+
return Number.isNaN(parsed) ? fallback : parsed;
|
|
50
|
+
};
|
|
51
|
+
const requireLoopsApiKey = () => {
|
|
52
|
+
const apiKey = process.env.LOOPS_API_KEY;
|
|
53
|
+
if (!apiKey) {
|
|
54
|
+
throw new Error("LOOPS_API_KEY environment variable must be set to use the HTTP API.");
|
|
55
|
+
}
|
|
56
|
+
return apiKey;
|
|
57
|
+
};
|
|
58
|
+
const respondError = (error) => {
|
|
59
|
+
console.error("[loops:http]", error);
|
|
60
|
+
const message = error instanceof Error ? error.message : "Unexpected error";
|
|
61
|
+
const status = error instanceof Error &&
|
|
62
|
+
error.message.includes("LOOPS_API_KEY environment variable")
|
|
63
|
+
? 500
|
|
64
|
+
: 400;
|
|
65
|
+
return jsonResponse({ error: message }, { status });
|
|
66
|
+
};
|
|
67
|
+
http.route({
|
|
68
|
+
pathPrefix: "/loops",
|
|
69
|
+
method: "OPTIONS",
|
|
70
|
+
handler: httpAction(async (_ctx, request) => {
|
|
71
|
+
const headers = buildCorsHeaders();
|
|
72
|
+
const requestedHeaders = request.headers.get("Access-Control-Request-Headers");
|
|
73
|
+
if (requestedHeaders) {
|
|
74
|
+
headers.set("Access-Control-Allow-Headers", requestedHeaders);
|
|
75
|
+
}
|
|
76
|
+
const requestedMethod = request.headers.get("Access-Control-Request-Method");
|
|
77
|
+
if (requestedMethod) {
|
|
78
|
+
headers.set("Access-Control-Allow-Methods", `${requestedMethod},OPTIONS`);
|
|
79
|
+
}
|
|
80
|
+
return new Response(null, { status: 204, headers });
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
83
|
+
http.route({
|
|
84
|
+
path: "/loops/contacts",
|
|
85
|
+
method: "POST",
|
|
86
|
+
handler: httpAction(async (ctx, request) => {
|
|
87
|
+
try {
|
|
88
|
+
const contact = await readJsonBody(request);
|
|
89
|
+
const data = await ctx.runAction(internalLib.addContact, {
|
|
90
|
+
apiKey: requireLoopsApiKey(),
|
|
91
|
+
contact,
|
|
92
|
+
});
|
|
93
|
+
return jsonResponse(data, { status: 201 });
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
return respondError(error);
|
|
97
|
+
}
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
http.route({
|
|
101
|
+
path: "/loops/contacts",
|
|
102
|
+
method: "PUT",
|
|
103
|
+
handler: httpAction(async (ctx, request) => {
|
|
104
|
+
try {
|
|
105
|
+
const payload = await readJsonBody(request);
|
|
106
|
+
if (!payload.email) {
|
|
107
|
+
throw new Error("email is required");
|
|
108
|
+
}
|
|
109
|
+
const data = await ctx.runAction(internalLib.updateContact, {
|
|
110
|
+
apiKey: requireLoopsApiKey(),
|
|
111
|
+
email: payload.email,
|
|
112
|
+
dataVariables: payload.dataVariables,
|
|
113
|
+
firstName: payload.firstName,
|
|
114
|
+
lastName: payload.lastName,
|
|
115
|
+
userId: payload.userId,
|
|
116
|
+
source: payload.source,
|
|
117
|
+
subscribed: payload.subscribed,
|
|
118
|
+
userGroup: payload.userGroup,
|
|
119
|
+
});
|
|
120
|
+
return jsonResponse(data);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
return respondError(error);
|
|
124
|
+
}
|
|
125
|
+
}),
|
|
126
|
+
});
|
|
127
|
+
http.route({
|
|
128
|
+
path: "/loops/contacts",
|
|
129
|
+
method: "GET",
|
|
130
|
+
handler: httpAction(async (ctx, request) => {
|
|
131
|
+
try {
|
|
132
|
+
const url = new URL(request.url);
|
|
133
|
+
const email = url.searchParams.get("email");
|
|
134
|
+
if (email) {
|
|
135
|
+
const data = await ctx.runAction(internalLib.findContact, {
|
|
136
|
+
apiKey: requireLoopsApiKey(),
|
|
137
|
+
email,
|
|
138
|
+
});
|
|
139
|
+
return jsonResponse(data);
|
|
140
|
+
}
|
|
141
|
+
const data = await ctx.runQuery(internalLib.listContacts, {
|
|
142
|
+
userGroup: url.searchParams.get("userGroup") ?? undefined,
|
|
143
|
+
source: url.searchParams.get("source") ?? undefined,
|
|
144
|
+
subscribed: booleanFromQuery(url.searchParams.get("subscribed")),
|
|
145
|
+
limit: numberFromQuery(url.searchParams.get("limit"), 100),
|
|
146
|
+
offset: numberFromQuery(url.searchParams.get("offset"), 0),
|
|
147
|
+
});
|
|
148
|
+
return jsonResponse(data);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
return respondError(error);
|
|
152
|
+
}
|
|
153
|
+
}),
|
|
154
|
+
});
|
|
155
|
+
http.route({
|
|
156
|
+
path: "/loops/contacts",
|
|
157
|
+
method: "DELETE",
|
|
158
|
+
handler: httpAction(async (ctx, request) => {
|
|
159
|
+
try {
|
|
160
|
+
const payload = await readJsonBody(request);
|
|
161
|
+
if (!payload.email) {
|
|
162
|
+
throw new Error("email is required");
|
|
163
|
+
}
|
|
164
|
+
await ctx.runAction(internalLib.deleteContact, {
|
|
165
|
+
apiKey: requireLoopsApiKey(),
|
|
166
|
+
email: payload.email,
|
|
167
|
+
});
|
|
168
|
+
return emptyResponse({ status: 204 });
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
return respondError(error);
|
|
172
|
+
}
|
|
173
|
+
}),
|
|
174
|
+
});
|
|
175
|
+
http.route({
|
|
176
|
+
path: "/loops/transactional",
|
|
177
|
+
method: "POST",
|
|
178
|
+
handler: httpAction(async (ctx, request) => {
|
|
179
|
+
try {
|
|
180
|
+
const payload = await readJsonBody(request);
|
|
181
|
+
if (!payload.transactionalId) {
|
|
182
|
+
throw new Error("transactionalId is required");
|
|
183
|
+
}
|
|
184
|
+
if (!payload.email) {
|
|
185
|
+
throw new Error("email is required");
|
|
186
|
+
}
|
|
187
|
+
const data = await ctx.runAction(internalLib.sendTransactional, {
|
|
188
|
+
apiKey: requireLoopsApiKey(),
|
|
189
|
+
transactionalId: payload.transactionalId,
|
|
190
|
+
email: payload.email,
|
|
191
|
+
dataVariables: payload.dataVariables,
|
|
192
|
+
});
|
|
193
|
+
return jsonResponse(data);
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
return respondError(error);
|
|
197
|
+
}
|
|
198
|
+
}),
|
|
199
|
+
});
|
|
200
|
+
http.route({
|
|
201
|
+
path: "/loops/events",
|
|
202
|
+
method: "POST",
|
|
203
|
+
handler: httpAction(async (ctx, request) => {
|
|
204
|
+
try {
|
|
205
|
+
const payload = await readJsonBody(request);
|
|
206
|
+
if (!payload.email) {
|
|
207
|
+
throw new Error("email is required");
|
|
208
|
+
}
|
|
209
|
+
if (!payload.eventName) {
|
|
210
|
+
throw new Error("eventName is required");
|
|
211
|
+
}
|
|
212
|
+
const data = await ctx.runAction(internalLib.sendEvent, {
|
|
213
|
+
apiKey: requireLoopsApiKey(),
|
|
214
|
+
email: payload.email,
|
|
215
|
+
eventName: payload.eventName,
|
|
216
|
+
eventProperties: payload.eventProperties,
|
|
217
|
+
});
|
|
218
|
+
return jsonResponse(data);
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
return respondError(error);
|
|
222
|
+
}
|
|
223
|
+
}),
|
|
224
|
+
});
|
|
225
|
+
http.route({
|
|
226
|
+
path: "/loops/trigger",
|
|
227
|
+
method: "POST",
|
|
228
|
+
handler: httpAction(async (ctx, request) => {
|
|
229
|
+
try {
|
|
230
|
+
const payload = await readJsonBody(request);
|
|
231
|
+
if (!payload.loopId) {
|
|
232
|
+
throw new Error("loopId is required");
|
|
233
|
+
}
|
|
234
|
+
if (!payload.email) {
|
|
235
|
+
throw new Error("email is required");
|
|
236
|
+
}
|
|
237
|
+
const data = await ctx.runAction(internalLib.triggerLoop, {
|
|
238
|
+
apiKey: requireLoopsApiKey(),
|
|
239
|
+
loopId: payload.loopId,
|
|
240
|
+
email: payload.email,
|
|
241
|
+
dataVariables: payload.dataVariables,
|
|
242
|
+
eventName: payload.eventName,
|
|
243
|
+
});
|
|
244
|
+
return jsonResponse(data);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
return respondError(error);
|
|
248
|
+
}
|
|
249
|
+
}),
|
|
250
|
+
});
|
|
251
|
+
http.route({
|
|
252
|
+
path: "/loops/stats",
|
|
253
|
+
method: "GET",
|
|
254
|
+
handler: httpAction(async (ctx, request) => {
|
|
255
|
+
try {
|
|
256
|
+
const url = new URL(request.url);
|
|
257
|
+
const timeWindowMs = numberFromQuery(url.searchParams.get("timeWindowMs"), 86400000);
|
|
258
|
+
const data = await ctx.runQuery(internalLib.getEmailStats, {
|
|
259
|
+
timeWindowMs,
|
|
260
|
+
});
|
|
261
|
+
return jsonResponse(data);
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
return respondError(error);
|
|
265
|
+
}
|
|
266
|
+
}),
|
|
267
|
+
});
|
|
268
|
+
export default http;
|
package/dist/component/lib.d.ts
CHANGED
|
@@ -1,48 +1,144 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Internal mutation to store/update a contact in the database
|
|
3
3
|
*/
|
|
4
|
-
export declare const storeContact:
|
|
4
|
+
export declare const storeContact: import("convex/server").RegisteredMutation<"public", {
|
|
5
|
+
email: string;
|
|
6
|
+
firstName?: string | undefined;
|
|
7
|
+
lastName?: string | undefined;
|
|
8
|
+
userId?: string | undefined;
|
|
9
|
+
source?: string | undefined;
|
|
10
|
+
subscribed?: boolean | undefined;
|
|
11
|
+
userGroup?: string | undefined;
|
|
12
|
+
loopsContactId?: string | undefined;
|
|
13
|
+
}, Promise<void>>;
|
|
5
14
|
/**
|
|
6
15
|
* Internal mutation to delete a contact from the database
|
|
7
16
|
*/
|
|
8
|
-
export declare const removeContact:
|
|
17
|
+
export declare const removeContact: import("convex/server").RegisteredMutation<"public", {
|
|
18
|
+
email: string;
|
|
19
|
+
}, Promise<void>>;
|
|
9
20
|
/**
|
|
10
21
|
* Internal mutation to log an email operation for monitoring
|
|
11
22
|
*/
|
|
12
|
-
export declare const logEmailOperation:
|
|
23
|
+
export declare const logEmailOperation: import("convex/server").RegisteredMutation<"public", {
|
|
24
|
+
operationType: "transactional" | "event" | "campaign" | "loop";
|
|
25
|
+
email: string;
|
|
26
|
+
success: boolean;
|
|
27
|
+
actorId?: string | undefined;
|
|
28
|
+
transactionalId?: string | undefined;
|
|
29
|
+
campaignId?: string | undefined;
|
|
30
|
+
loopId?: string | undefined;
|
|
31
|
+
eventName?: string | undefined;
|
|
32
|
+
messageId?: string | undefined;
|
|
33
|
+
metadata?: Record<string, any> | undefined;
|
|
34
|
+
}, Promise<void>>;
|
|
13
35
|
/**
|
|
14
36
|
* Count contacts in the database
|
|
15
37
|
* Can filter by audience criteria (userGroup, source, subscribed status)
|
|
16
38
|
*/
|
|
17
|
-
export declare const countContacts:
|
|
39
|
+
export declare const countContacts: import("convex/server").RegisteredQuery<"public", {
|
|
40
|
+
userGroup?: string | undefined;
|
|
41
|
+
source?: string | undefined;
|
|
42
|
+
subscribed?: boolean | undefined;
|
|
43
|
+
}, Promise<number>>;
|
|
18
44
|
/**
|
|
19
45
|
* List contacts from the database with pagination
|
|
20
46
|
* Can filter by audience criteria (userGroup, source, subscribed status)
|
|
21
47
|
* Returns actual contact data, not just a count
|
|
22
48
|
*/
|
|
23
|
-
export declare const listContacts:
|
|
49
|
+
export declare const listContacts: import("convex/server").RegisteredQuery<"public", {
|
|
50
|
+
limit: number;
|
|
51
|
+
offset: number;
|
|
52
|
+
userGroup?: string | undefined;
|
|
53
|
+
source?: string | undefined;
|
|
54
|
+
subscribed?: boolean | undefined;
|
|
55
|
+
}, Promise<{
|
|
56
|
+
contacts: {
|
|
57
|
+
_id: string;
|
|
58
|
+
email: string;
|
|
59
|
+
subscribed: boolean;
|
|
60
|
+
createdAt: number;
|
|
61
|
+
updatedAt: number;
|
|
62
|
+
firstName?: string | undefined;
|
|
63
|
+
lastName?: string | undefined;
|
|
64
|
+
userId?: string | undefined;
|
|
65
|
+
source?: string | undefined;
|
|
66
|
+
userGroup?: string | undefined;
|
|
67
|
+
loopsContactId?: string | undefined;
|
|
68
|
+
}[];
|
|
69
|
+
total: number;
|
|
70
|
+
limit: number;
|
|
71
|
+
offset: number;
|
|
72
|
+
hasMore: boolean;
|
|
73
|
+
}>>;
|
|
24
74
|
/**
|
|
25
75
|
* Add or update a contact in Loops
|
|
26
76
|
* This function tries to create a contact, and if the email already exists (409),
|
|
27
77
|
* it falls back to updating the contact instead.
|
|
28
78
|
*/
|
|
29
|
-
export declare const addContact:
|
|
79
|
+
export declare const addContact: import("convex/server").RegisteredAction<"public", {
|
|
80
|
+
apiKey: string;
|
|
81
|
+
contact: {
|
|
82
|
+
email: string;
|
|
83
|
+
firstName?: string | undefined;
|
|
84
|
+
lastName?: string | undefined;
|
|
85
|
+
userId?: string | undefined;
|
|
86
|
+
source?: string | undefined;
|
|
87
|
+
subscribed?: boolean | undefined;
|
|
88
|
+
userGroup?: string | undefined;
|
|
89
|
+
};
|
|
90
|
+
}, Promise<{
|
|
91
|
+
success: boolean;
|
|
92
|
+
id?: string | undefined;
|
|
93
|
+
}>>;
|
|
30
94
|
/**
|
|
31
95
|
* Update an existing contact in Loops
|
|
32
96
|
*/
|
|
33
|
-
export declare const updateContact:
|
|
97
|
+
export declare const updateContact: import("convex/server").RegisteredAction<"public", {
|
|
98
|
+
apiKey: string;
|
|
99
|
+
email: string;
|
|
100
|
+
dataVariables?: Record<string, any> | undefined;
|
|
101
|
+
firstName?: string | undefined;
|
|
102
|
+
lastName?: string | undefined;
|
|
103
|
+
userId?: string | undefined;
|
|
104
|
+
source?: string | undefined;
|
|
105
|
+
subscribed?: boolean | undefined;
|
|
106
|
+
userGroup?: string | undefined;
|
|
107
|
+
}, Promise<{
|
|
108
|
+
success: boolean;
|
|
109
|
+
}>>;
|
|
34
110
|
/**
|
|
35
111
|
* Send a transactional email using a transactional ID
|
|
36
112
|
*/
|
|
37
|
-
export declare const sendTransactional:
|
|
113
|
+
export declare const sendTransactional: import("convex/server").RegisteredAction<"public", {
|
|
114
|
+
apiKey: string;
|
|
115
|
+
transactionalId: string;
|
|
116
|
+
email: string;
|
|
117
|
+
dataVariables?: Record<string, any> | undefined;
|
|
118
|
+
}, Promise<{
|
|
119
|
+
success: boolean;
|
|
120
|
+
messageId?: string | undefined;
|
|
121
|
+
}>>;
|
|
38
122
|
/**
|
|
39
123
|
* Send an event to Loops to trigger email workflows
|
|
40
124
|
*/
|
|
41
|
-
export declare const sendEvent:
|
|
125
|
+
export declare const sendEvent: import("convex/server").RegisteredAction<"public", {
|
|
126
|
+
apiKey: string;
|
|
127
|
+
email: string;
|
|
128
|
+
eventName: string;
|
|
129
|
+
eventProperties?: Record<string, any> | undefined;
|
|
130
|
+
}, Promise<{
|
|
131
|
+
success: boolean;
|
|
132
|
+
}>>;
|
|
42
133
|
/**
|
|
43
134
|
* Delete a contact from Loops
|
|
44
135
|
*/
|
|
45
|
-
export declare const deleteContact:
|
|
136
|
+
export declare const deleteContact: import("convex/server").RegisteredAction<"public", {
|
|
137
|
+
apiKey: string;
|
|
138
|
+
email: string;
|
|
139
|
+
}, Promise<{
|
|
140
|
+
success: boolean;
|
|
141
|
+
}>>;
|
|
46
142
|
/**
|
|
47
143
|
* Trigger a loop for a contact
|
|
48
144
|
* Note: Loops in Loops.so are triggered through events, not a direct API endpoint.
|
|
@@ -59,61 +155,180 @@ export declare const deleteContact: any;
|
|
|
59
155
|
*
|
|
60
156
|
* This function is kept for backwards compatibility but works by sending an event.
|
|
61
157
|
*/
|
|
62
|
-
export declare const triggerLoop:
|
|
158
|
+
export declare const triggerLoop: import("convex/server").RegisteredAction<"public", {
|
|
159
|
+
apiKey: string;
|
|
160
|
+
loopId: string;
|
|
161
|
+
email: string;
|
|
162
|
+
dataVariables?: Record<string, any> | undefined;
|
|
163
|
+
eventName?: string | undefined;
|
|
164
|
+
}, Promise<{
|
|
165
|
+
success: boolean;
|
|
166
|
+
warning?: string | undefined;
|
|
167
|
+
}>>;
|
|
63
168
|
/**
|
|
64
169
|
* Find a contact by email
|
|
65
170
|
* Retrieves contact information from Loops
|
|
66
171
|
* Note: Loops API may return either an object or an array
|
|
67
172
|
*/
|
|
68
|
-
export declare const findContact:
|
|
173
|
+
export declare const findContact: import("convex/server").RegisteredAction<"public", {
|
|
174
|
+
apiKey: string;
|
|
175
|
+
email: string;
|
|
176
|
+
}, Promise<{
|
|
177
|
+
success: boolean;
|
|
178
|
+
contact?: {
|
|
179
|
+
id?: string | null | undefined;
|
|
180
|
+
email?: string | null | undefined;
|
|
181
|
+
firstName?: string | null | undefined;
|
|
182
|
+
lastName?: string | null | undefined;
|
|
183
|
+
source?: string | null | undefined;
|
|
184
|
+
subscribed?: boolean | null | undefined;
|
|
185
|
+
userGroup?: string | null | undefined;
|
|
186
|
+
userId?: string | null | undefined;
|
|
187
|
+
createdAt?: string | null | undefined;
|
|
188
|
+
} | undefined;
|
|
189
|
+
}>>;
|
|
69
190
|
/**
|
|
70
191
|
* Batch create contacts
|
|
71
192
|
* Creates multiple contacts sequentially using the single contact create endpoint.
|
|
72
193
|
* Note: Loops.so doesn't have a batch endpoint, so we create contacts one by one.
|
|
73
194
|
*/
|
|
74
|
-
export declare const batchCreateContacts:
|
|
195
|
+
export declare const batchCreateContacts: import("convex/server").RegisteredAction<"public", {
|
|
196
|
+
apiKey: string;
|
|
197
|
+
contacts: {
|
|
198
|
+
email: string;
|
|
199
|
+
firstName?: string | undefined;
|
|
200
|
+
lastName?: string | undefined;
|
|
201
|
+
userId?: string | undefined;
|
|
202
|
+
source?: string | undefined;
|
|
203
|
+
subscribed?: boolean | undefined;
|
|
204
|
+
userGroup?: string | undefined;
|
|
205
|
+
}[];
|
|
206
|
+
}, Promise<{
|
|
207
|
+
success: boolean;
|
|
208
|
+
created?: number | undefined;
|
|
209
|
+
failed?: number | undefined;
|
|
210
|
+
results?: {
|
|
211
|
+
email: string;
|
|
212
|
+
success: boolean;
|
|
213
|
+
error?: string | undefined;
|
|
214
|
+
}[] | undefined;
|
|
215
|
+
}>>;
|
|
75
216
|
/**
|
|
76
217
|
* Unsubscribe a contact
|
|
77
218
|
* Unsubscribes a contact from receiving emails (they remain in the system)
|
|
78
219
|
*/
|
|
79
|
-
export declare const unsubscribeContact:
|
|
220
|
+
export declare const unsubscribeContact: import("convex/server").RegisteredAction<"public", {
|
|
221
|
+
apiKey: string;
|
|
222
|
+
email: string;
|
|
223
|
+
}, Promise<{
|
|
224
|
+
success: boolean;
|
|
225
|
+
}>>;
|
|
80
226
|
/**
|
|
81
227
|
* Resubscribe a contact
|
|
82
228
|
* Resubscribes a previously unsubscribed contact
|
|
83
229
|
*/
|
|
84
|
-
export declare const resubscribeContact:
|
|
230
|
+
export declare const resubscribeContact: import("convex/server").RegisteredAction<"public", {
|
|
231
|
+
apiKey: string;
|
|
232
|
+
email: string;
|
|
233
|
+
}, Promise<{
|
|
234
|
+
success: boolean;
|
|
235
|
+
}>>;
|
|
85
236
|
/**
|
|
86
237
|
* Check for spam patterns: too many emails to the same recipient in a time window
|
|
87
238
|
* Returns email addresses that received too many emails
|
|
88
239
|
*/
|
|
89
|
-
export declare const detectRecipientSpam:
|
|
240
|
+
export declare const detectRecipientSpam: import("convex/server").RegisteredQuery<"public", {
|
|
241
|
+
timeWindowMs: number;
|
|
242
|
+
maxEmailsPerRecipient: number;
|
|
243
|
+
}, Promise<{
|
|
244
|
+
[x: string]: any;
|
|
245
|
+
email: string;
|
|
246
|
+
count: number;
|
|
247
|
+
timeWindowMs: number;
|
|
248
|
+
}[]>>;
|
|
90
249
|
/**
|
|
91
250
|
* Check for spam patterns: too many emails from the same actor/user
|
|
92
251
|
* Returns actor IDs that sent too many emails
|
|
93
252
|
*/
|
|
94
|
-
export declare const detectActorSpam:
|
|
253
|
+
export declare const detectActorSpam: import("convex/server").RegisteredQuery<"public", {
|
|
254
|
+
timeWindowMs: number;
|
|
255
|
+
maxEmailsPerActor: number;
|
|
256
|
+
}, Promise<{
|
|
257
|
+
actorId: string;
|
|
258
|
+
count: number;
|
|
259
|
+
timeWindowMs: number;
|
|
260
|
+
}[]>>;
|
|
95
261
|
/**
|
|
96
262
|
* Get recent email operation statistics for monitoring
|
|
97
263
|
*/
|
|
98
|
-
export declare const getEmailStats:
|
|
264
|
+
export declare const getEmailStats: import("convex/server").RegisteredQuery<"public", {
|
|
265
|
+
timeWindowMs: number;
|
|
266
|
+
}, Promise<{
|
|
267
|
+
[x: string]: any;
|
|
268
|
+
totalOperations: number;
|
|
269
|
+
successfulOperations: number;
|
|
270
|
+
failedOperations: number;
|
|
271
|
+
operationsByType: Record<string, number>;
|
|
272
|
+
uniqueRecipients: number;
|
|
273
|
+
uniqueActors: number;
|
|
274
|
+
}>>;
|
|
99
275
|
/**
|
|
100
276
|
* Detect rapid-fire email sending patterns (multiple emails sent in quick succession)
|
|
101
277
|
* Returns suspicious patterns indicating potential spam
|
|
102
278
|
*/
|
|
103
|
-
export declare const detectRapidFirePatterns:
|
|
279
|
+
export declare const detectRapidFirePatterns: import("convex/server").RegisteredQuery<"public", {
|
|
280
|
+
timeWindowMs: number;
|
|
281
|
+
minEmailsInWindow: number;
|
|
282
|
+
}, Promise<{
|
|
283
|
+
count: number;
|
|
284
|
+
timeWindowMs: number;
|
|
285
|
+
firstTimestamp: number;
|
|
286
|
+
lastTimestamp: number;
|
|
287
|
+
email?: string | undefined;
|
|
288
|
+
actorId?: string | undefined;
|
|
289
|
+
}[]>>;
|
|
104
290
|
/**
|
|
105
291
|
* Rate limiting: Check if an email can be sent to a recipient
|
|
106
292
|
* Based on recent email operations in the database
|
|
107
293
|
*/
|
|
108
|
-
export declare const checkRecipientRateLimit:
|
|
294
|
+
export declare const checkRecipientRateLimit: import("convex/server").RegisteredQuery<"public", {
|
|
295
|
+
email: string;
|
|
296
|
+
timeWindowMs: number;
|
|
297
|
+
maxEmails: number;
|
|
298
|
+
}, Promise<{
|
|
299
|
+
[x: string]: any;
|
|
300
|
+
allowed: boolean;
|
|
301
|
+
count: number;
|
|
302
|
+
limit: number;
|
|
303
|
+
timeWindowMs: number;
|
|
304
|
+
retryAfter?: number | undefined;
|
|
305
|
+
}>>;
|
|
109
306
|
/**
|
|
110
307
|
* Rate limiting: Check if an actor/user can send more emails
|
|
111
308
|
* Based on recent email operations in the database
|
|
112
309
|
*/
|
|
113
|
-
export declare const checkActorRateLimit:
|
|
310
|
+
export declare const checkActorRateLimit: import("convex/server").RegisteredQuery<"public", {
|
|
311
|
+
actorId: string;
|
|
312
|
+
timeWindowMs: number;
|
|
313
|
+
maxEmails: number;
|
|
314
|
+
}, Promise<{
|
|
315
|
+
allowed: boolean;
|
|
316
|
+
count: number;
|
|
317
|
+
limit: number;
|
|
318
|
+
timeWindowMs: number;
|
|
319
|
+
retryAfter?: number | undefined;
|
|
320
|
+
}>>;
|
|
114
321
|
/**
|
|
115
322
|
* Rate limiting: Check global email sending rate
|
|
116
323
|
* Checks total email operations across all senders
|
|
117
324
|
*/
|
|
118
|
-
export declare const checkGlobalRateLimit:
|
|
325
|
+
export declare const checkGlobalRateLimit: import("convex/server").RegisteredQuery<"public", {
|
|
326
|
+
timeWindowMs: number;
|
|
327
|
+
maxEmails: number;
|
|
328
|
+
}, Promise<{
|
|
329
|
+
allowed: boolean;
|
|
330
|
+
count: number;
|
|
331
|
+
limit: number;
|
|
332
|
+
timeWindowMs: number;
|
|
333
|
+
}>>;
|
|
119
334
|
//# 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":"AAOA;;GAEG;AACH,eAAO,MAAM,YAAY;;;;;;;;;iBA6CvB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa;;iBAexB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;iBAkC5B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,aAAa;;;;mBAwCxB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;GAmFvB,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;;;;;;;GA4BpB,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;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;KA8C9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,eAAe;;;;;;;KA4C1B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;GAkDxB,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;KAsGlC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;GA+ClC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;GA6C9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;GA8B/B,CAAC"}
|