@niama/loops 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/README.md +506 -0
- package/dist/client/index.d.ts +510 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +464 -0
- package/dist/component/_generated/api.d.ts +232 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +30 -0
- package/dist/component/_generated/component.d.ts +245 -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/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +77 -0
- package/dist/component/actions.d.ts +159 -0
- package/dist/component/actions.d.ts.map +1 -0
- package/dist/component/actions.js +468 -0
- 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 +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +5 -0
- package/dist/component/helpers.d.ts +16 -0
- package/dist/component/helpers.d.ts.map +1 -0
- package/dist/component/helpers.js +98 -0
- package/dist/component/http.d.ts +3 -0
- package/dist/component/http.d.ts.map +1 -0
- package/dist/component/http.js +208 -0
- package/dist/component/mutations.d.ts +55 -0
- package/dist/component/mutations.d.ts.map +1 -0
- package/dist/component/mutations.js +167 -0
- package/dist/component/queries.d.ts +171 -0
- package/dist/component/queries.d.ts.map +1 -0
- package/dist/component/queries.js +516 -0
- package/dist/component/schema.d.ts +63 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +16 -0
- package/dist/component/tables/contacts.d.ts +16 -0
- package/dist/component/tables/contacts.d.ts.map +1 -0
- package/dist/component/tables/contacts.js +16 -0
- package/dist/component/tables/emailOperations.d.ts +17 -0
- package/dist/component/tables/emailOperations.d.ts.map +1 -0
- package/dist/component/tables/emailOperations.js +17 -0
- package/dist/component/validators.d.ts +338 -0
- package/dist/component/validators.d.ts.map +1 -0
- package/dist/component/validators.js +167 -0
- package/dist/test.d.ts +78 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +16 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +0 -0
- package/package.json +112 -0
- package/src/client/index.ts +618 -0
- package/src/component/_generated/api.ts +253 -0
- package/src/component/_generated/component.ts +291 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +161 -0
- package/src/component/actions.ts +556 -0
- package/src/component/aggregates.ts +89 -0
- package/src/component/convex.config.ts +8 -0
- package/src/component/helpers.ts +130 -0
- package/src/component/http.ts +236 -0
- package/src/component/mutations.ts +192 -0
- package/src/component/queries.ts +604 -0
- package/src/component/schema.ts +17 -0
- package/src/component/tables/contacts.ts +17 -0
- package/src/component/tables/emailOperations.ts +23 -0
- package/src/component/validators.ts +197 -0
- package/src/test.ts +27 -0
- package/src/types.ts +62 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validators for Loops API requests and component operations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validator for contact data
|
|
9
|
+
* Used for creating and updating contacts
|
|
10
|
+
*/
|
|
11
|
+
export const contactValidator = v.object({
|
|
12
|
+
email: v.string(),
|
|
13
|
+
firstName: v.optional(v.string()),
|
|
14
|
+
lastName: v.optional(v.string()),
|
|
15
|
+
userId: v.optional(v.string()),
|
|
16
|
+
source: v.optional(v.string()),
|
|
17
|
+
subscribed: v.optional(v.boolean()),
|
|
18
|
+
userGroup: v.optional(v.string()),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validator for transactional email requests
|
|
23
|
+
*/
|
|
24
|
+
export const transactionalEmailValidator = v.object({
|
|
25
|
+
transactionalId: v.optional(v.string()),
|
|
26
|
+
email: v.string(),
|
|
27
|
+
dataVariables: v.optional(v.record(v.string(), v.any())),
|
|
28
|
+
idempotencyKey: v.optional(v.string()),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Validator for event requests
|
|
33
|
+
* Used for sending events that trigger email workflows
|
|
34
|
+
*/
|
|
35
|
+
export const eventValidator = v.object({
|
|
36
|
+
email: v.string(),
|
|
37
|
+
eventName: v.string(),
|
|
38
|
+
eventProperties: v.optional(v.record(v.string(), v.any())),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Validator for operation type enum
|
|
43
|
+
*/
|
|
44
|
+
export const operationTypeValidator = v.union(
|
|
45
|
+
v.literal("transactional"),
|
|
46
|
+
v.literal("event"),
|
|
47
|
+
v.literal("campaign"),
|
|
48
|
+
v.literal("loop"),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Common return validators used across functions
|
|
53
|
+
*/
|
|
54
|
+
export const successResponseValidator = v.object({
|
|
55
|
+
success: v.boolean(),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const successWithIdResponseValidator = v.object({
|
|
59
|
+
success: v.boolean(),
|
|
60
|
+
id: v.optional(v.string()),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export const successWithMessageIdResponseValidator = v.object({
|
|
64
|
+
success: v.boolean(),
|
|
65
|
+
messageId: v.optional(v.string()),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export const successWithWarningResponseValidator = v.object({
|
|
69
|
+
success: v.boolean(),
|
|
70
|
+
warning: v.optional(v.string()),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Contact document validator for query returns
|
|
75
|
+
*/
|
|
76
|
+
export const contactDocValidator = v.object({
|
|
77
|
+
_id: v.string(),
|
|
78
|
+
email: v.string(),
|
|
79
|
+
firstName: v.optional(v.string()),
|
|
80
|
+
lastName: v.optional(v.string()),
|
|
81
|
+
userId: v.optional(v.string()),
|
|
82
|
+
source: v.optional(v.string()),
|
|
83
|
+
subscribed: v.boolean(),
|
|
84
|
+
userGroup: v.optional(v.string()),
|
|
85
|
+
loopsContactId: v.optional(v.string()),
|
|
86
|
+
createdAt: v.number(),
|
|
87
|
+
updatedAt: v.number(),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Paginated contacts response validator
|
|
92
|
+
*/
|
|
93
|
+
export const paginatedContactsResponseValidator = v.object({
|
|
94
|
+
contacts: v.array(contactDocValidator),
|
|
95
|
+
continueCursor: v.union(v.string(), v.null()),
|
|
96
|
+
isDone: v.boolean(),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Rate limit response validator
|
|
101
|
+
*/
|
|
102
|
+
export const rateLimitResponseValidator = v.object({
|
|
103
|
+
allowed: v.boolean(),
|
|
104
|
+
count: v.number(),
|
|
105
|
+
limit: v.number(),
|
|
106
|
+
timeWindowMs: v.number(),
|
|
107
|
+
retryAfter: v.optional(v.number()),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Email stats response validator
|
|
112
|
+
*/
|
|
113
|
+
export const emailStatsResponseValidator = v.object({
|
|
114
|
+
totalOperations: v.number(),
|
|
115
|
+
successfulOperations: v.number(),
|
|
116
|
+
failedOperations: v.number(),
|
|
117
|
+
operationsByType: v.record(v.string(), v.number()),
|
|
118
|
+
uniqueRecipients: v.number(),
|
|
119
|
+
uniqueActors: v.number(),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Spam detection validators
|
|
124
|
+
*/
|
|
125
|
+
export const recipientSpamValidator = v.object({
|
|
126
|
+
email: v.string(),
|
|
127
|
+
count: v.number(),
|
|
128
|
+
timeWindowMs: v.number(),
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
export const actorSpamValidator = v.object({
|
|
132
|
+
actorId: v.string(),
|
|
133
|
+
count: v.number(),
|
|
134
|
+
timeWindowMs: v.number(),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
export const rapidFirePatternValidator = v.object({
|
|
138
|
+
email: v.optional(v.string()),
|
|
139
|
+
actorId: v.optional(v.string()),
|
|
140
|
+
count: v.number(),
|
|
141
|
+
timeWindowMs: v.number(),
|
|
142
|
+
firstTimestamp: v.number(),
|
|
143
|
+
lastTimestamp: v.number(),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Batch create response validator
|
|
148
|
+
*/
|
|
149
|
+
export const batchCreateResponseValidator = v.object({
|
|
150
|
+
success: v.boolean(),
|
|
151
|
+
created: v.optional(v.number()),
|
|
152
|
+
failed: v.optional(v.number()),
|
|
153
|
+
results: v.optional(
|
|
154
|
+
v.array(
|
|
155
|
+
v.object({
|
|
156
|
+
email: v.string(),
|
|
157
|
+
success: v.boolean(),
|
|
158
|
+
error: v.optional(v.string()),
|
|
159
|
+
}),
|
|
160
|
+
),
|
|
161
|
+
),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Find contact response validator
|
|
166
|
+
* Note: Loops API returns additional fields beyond our contact model
|
|
167
|
+
*/
|
|
168
|
+
export const findContactResponseValidator = v.object({
|
|
169
|
+
success: v.boolean(),
|
|
170
|
+
contact: v.optional(
|
|
171
|
+
v.object({
|
|
172
|
+
id: v.optional(v.union(v.string(), v.null())),
|
|
173
|
+
email: v.optional(v.union(v.string(), v.null())),
|
|
174
|
+
firstName: v.optional(v.union(v.string(), v.null())),
|
|
175
|
+
lastName: v.optional(v.union(v.string(), v.null())),
|
|
176
|
+
source: v.optional(v.union(v.string(), v.null())),
|
|
177
|
+
subscribed: v.optional(v.union(v.boolean(), v.null())),
|
|
178
|
+
userGroup: v.optional(v.union(v.string(), v.null())),
|
|
179
|
+
userId: v.optional(v.union(v.string(), v.null())),
|
|
180
|
+
createdAt: v.optional(v.union(v.string(), v.null())),
|
|
181
|
+
// Additional fields from Loops API
|
|
182
|
+
audienceId: v.optional(v.union(v.string(), v.null())),
|
|
183
|
+
timestamp: v.optional(v.union(v.string(), v.null())),
|
|
184
|
+
dataVariables: v.optional(v.any()),
|
|
185
|
+
mailingLists: v.optional(v.any()),
|
|
186
|
+
}),
|
|
187
|
+
),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Backfill response validator
|
|
192
|
+
*/
|
|
193
|
+
export const backfillResponseValidator = v.object({
|
|
194
|
+
processed: v.number(),
|
|
195
|
+
cursor: v.union(v.string(), v.null()),
|
|
196
|
+
isDone: v.boolean(),
|
|
197
|
+
});
|
package/src/test.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { TestConvex } from "convex-test";
|
|
2
|
+
import type { GenericSchema, SchemaDefinition } from "convex/server";
|
|
3
|
+
import schema from "./component/schema.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Register the Loops component with the test convex instance.
|
|
7
|
+
*
|
|
8
|
+
* @param t - The test convex instance from convexTest().
|
|
9
|
+
* @param name - The name of the component as registered in convex.config.ts.
|
|
10
|
+
* @param modules - The modules object from import.meta.glob. Required.
|
|
11
|
+
*/
|
|
12
|
+
export function register(
|
|
13
|
+
t: TestConvex<SchemaDefinition<GenericSchema, boolean>>,
|
|
14
|
+
name: string = "loops",
|
|
15
|
+
modules?: Record<string, () => Promise<unknown>>,
|
|
16
|
+
) {
|
|
17
|
+
if (!modules) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
"modules parameter is required. Pass import.meta.glob from your test file.",
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
t.registerComponent(name, schema, modules);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { schema };
|
|
26
|
+
|
|
27
|
+
export default { register, schema };
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GenericActionCtx,
|
|
3
|
+
GenericDataModel,
|
|
4
|
+
GenericMutationCtx,
|
|
5
|
+
GenericQueryCtx,
|
|
6
|
+
} from "convex/server";
|
|
7
|
+
|
|
8
|
+
// Convenient types for `ctx` args, that only include the bare minimum.
|
|
9
|
+
export type RunQueryCtx = Pick<GenericQueryCtx<GenericDataModel>, "runQuery">;
|
|
10
|
+
|
|
11
|
+
export type RunMutationCtx = Pick<
|
|
12
|
+
GenericMutationCtx<GenericDataModel>,
|
|
13
|
+
"runQuery" | "runMutation"
|
|
14
|
+
>;
|
|
15
|
+
|
|
16
|
+
export type RunActionCtx = Pick<
|
|
17
|
+
GenericActionCtx<GenericDataModel>,
|
|
18
|
+
"runQuery" | "runMutation" | "runAction"
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
// Type for Headers constructor parameter
|
|
22
|
+
export type HeadersInitParam = ConstructorParameters<typeof Headers>[0];
|
|
23
|
+
|
|
24
|
+
// Payload types for the Loops API
|
|
25
|
+
export interface ContactPayload {
|
|
26
|
+
email: string;
|
|
27
|
+
firstName?: string;
|
|
28
|
+
lastName?: string;
|
|
29
|
+
userId?: string;
|
|
30
|
+
source?: string;
|
|
31
|
+
subscribed?: boolean;
|
|
32
|
+
userGroup?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface UpdateContactPayload extends Partial<ContactPayload> {
|
|
36
|
+
email: string;
|
|
37
|
+
dataVariables?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface DeleteContactPayload {
|
|
41
|
+
email?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface TransactionalPayload {
|
|
45
|
+
transactionalId?: string;
|
|
46
|
+
email?: string;
|
|
47
|
+
dataVariables?: Record<string, unknown>;
|
|
48
|
+
idempotencyKey?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface EventPayload {
|
|
52
|
+
email?: string;
|
|
53
|
+
eventName?: string;
|
|
54
|
+
eventProperties?: Record<string, unknown>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface TriggerPayload {
|
|
58
|
+
loopId?: string;
|
|
59
|
+
email?: string;
|
|
60
|
+
dataVariables?: Record<string, unknown>;
|
|
61
|
+
eventName?: string;
|
|
62
|
+
}
|