vibesuite 1.3.3 → 2.0.2
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 +75 -6
- package/assets/.agent/skills/avoid-feature-creep/SKILL.md +307 -0
- package/assets/.agent/skills/avoid-feature-creep/agents/openai.yaml +3 -0
- package/assets/.agent/skills/avoid-feature-creep/assets/large-logo.png +0 -0
- package/assets/.agent/skills/avoid-feature-creep/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex/SKILL.md +62 -0
- package/assets/.agent/skills/convex/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-agents/SKILL.md +516 -0
- package/assets/.agent/skills/convex-agents/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-agents/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-agents/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-best-practices/SKILL.md +369 -0
- package/assets/.agent/skills/convex-best-practices/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-best-practices/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-best-practices/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-component-authoring/SKILL.md +457 -0
- package/assets/.agent/skills/convex-component-authoring/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-component-authoring/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-component-authoring/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-cron-jobs/SKILL.md +604 -0
- package/assets/.agent/skills/convex-cron-jobs/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-cron-jobs/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-cron-jobs/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-file-storage/SKILL.md +467 -0
- package/assets/.agent/skills/convex-file-storage/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-file-storage/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-file-storage/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-functions/SKILL.md +458 -0
- package/assets/.agent/skills/convex-functions/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-functions/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-functions/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-http-actions/SKILL.md +733 -0
- package/assets/.agent/skills/convex-http-actions/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-http-actions/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-http-actions/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-migrations/SKILL.md +712 -0
- package/assets/.agent/skills/convex-migrations/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-migrations/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-migrations/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-realtime/SKILL.md +443 -0
- package/assets/.agent/skills/convex-realtime/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-realtime/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-realtime/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-schema-validator/SKILL.md +400 -0
- package/assets/.agent/skills/convex-schema-validator/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-schema-validator/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-schema-validator/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-security-audit/SKILL.md +539 -0
- package/assets/.agent/skills/convex-security-audit/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-security-audit/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-security-audit/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-security-check/SKILL.md +378 -0
- package/assets/.agent/skills/convex-security-check/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-security-check/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-security-check/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/github-ops/SKILL.md +4 -4
- package/assets/.agent/skills/google-trends/SKILL.md +7 -7
- package/assets/.agent/skills/optimize-agent-context/SKILL.md +97 -0
- package/assets/.agent/skills/youtube-pipeline/SKILL.md +10 -10
- package/assets/.agent/workflows/LEGACY/init_smart_ops.md +2 -2
- package/assets/.agent/workflows/agent_reset.md +4 -6
- package/assets/.agent/workflows/mode-orchestrator.md +17 -22
- package/assets/.agent/workflows/mode-visionary.md +3 -10
- package/assets/.agent/workflows/optimize-agent-context.md +54 -0
- package/assets/.agent/workflows/remotion-build.md +17 -17
- package/assets/.agent/workflows/stitch.md +4 -4
- package/assets/VibeCode-Agents/vibe-orchestrator.yaml +14 -33
- package/assets/VibeCode-Agents/vibe-visionary.yaml +3 -13
- package/package.json +1 -1
- package/src/cli.js +416 -20
- package/src/harness.js +281 -0
- package/src/store.js +239 -0
- package/assets/VibeCode-Agents/custom_modes.yaml +0 -979
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: convex-cron-jobs
|
|
3
|
+
displayName: Convex Cron Jobs
|
|
4
|
+
description: Scheduled function patterns for background tasks including interval scheduling, cron expressions, job monitoring, retry strategies, and best practices for long-running tasks
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
author: Convex
|
|
7
|
+
tags: [convex, cron, scheduling, background-jobs, automation]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Convex Cron Jobs
|
|
11
|
+
|
|
12
|
+
Schedule recurring functions for background tasks, cleanup jobs, data syncing, and automated workflows in Convex applications.
|
|
13
|
+
|
|
14
|
+
## Documentation Sources
|
|
15
|
+
|
|
16
|
+
Before implementing, do not assume; fetch the latest documentation:
|
|
17
|
+
|
|
18
|
+
- Primary: https://docs.convex.dev/scheduling/cron-jobs
|
|
19
|
+
- Scheduling Overview: https://docs.convex.dev/scheduling
|
|
20
|
+
- Scheduled Functions: https://docs.convex.dev/scheduling/scheduled-functions
|
|
21
|
+
- For broader context: https://docs.convex.dev/llms.txt
|
|
22
|
+
|
|
23
|
+
## Instructions
|
|
24
|
+
|
|
25
|
+
### Cron Jobs Overview
|
|
26
|
+
|
|
27
|
+
Convex cron jobs allow you to schedule functions to run at regular intervals or specific times. Key features:
|
|
28
|
+
|
|
29
|
+
- Run functions on a fixed schedule
|
|
30
|
+
- Support for interval-based and cron expression scheduling
|
|
31
|
+
- Automatic retries on failure
|
|
32
|
+
- Monitoring via the Convex dashboard
|
|
33
|
+
|
|
34
|
+
### Basic Cron Setup
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// convex/crons.ts
|
|
38
|
+
import { cronJobs } from "convex/server";
|
|
39
|
+
import { internal } from "./_generated/api";
|
|
40
|
+
|
|
41
|
+
const crons = cronJobs();
|
|
42
|
+
|
|
43
|
+
// Run every hour
|
|
44
|
+
crons.interval(
|
|
45
|
+
"cleanup expired sessions",
|
|
46
|
+
{ hours: 1 },
|
|
47
|
+
internal.tasks.cleanupExpiredSessions,
|
|
48
|
+
{}
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Run every day at midnight UTC
|
|
52
|
+
crons.cron(
|
|
53
|
+
"daily report",
|
|
54
|
+
"0 0 * * *",
|
|
55
|
+
internal.reports.generateDailyReport,
|
|
56
|
+
{}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
export default crons;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Interval-Based Scheduling
|
|
63
|
+
|
|
64
|
+
Use `crons.interval` for simple recurring tasks:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// convex/crons.ts
|
|
68
|
+
import { cronJobs } from "convex/server";
|
|
69
|
+
import { internal } from "./_generated/api";
|
|
70
|
+
|
|
71
|
+
const crons = cronJobs();
|
|
72
|
+
|
|
73
|
+
// Every 5 minutes
|
|
74
|
+
crons.interval(
|
|
75
|
+
"sync external data",
|
|
76
|
+
{ minutes: 5 },
|
|
77
|
+
internal.sync.fetchExternalData,
|
|
78
|
+
{}
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Every 2 hours
|
|
82
|
+
crons.interval(
|
|
83
|
+
"cleanup temp files",
|
|
84
|
+
{ hours: 2 },
|
|
85
|
+
internal.files.cleanupTempFiles,
|
|
86
|
+
{}
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Every 30 seconds (minimum interval)
|
|
90
|
+
crons.interval(
|
|
91
|
+
"health check",
|
|
92
|
+
{ seconds: 30 },
|
|
93
|
+
internal.monitoring.healthCheck,
|
|
94
|
+
{}
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
export default crons;
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Cron Expression Scheduling
|
|
101
|
+
|
|
102
|
+
Use `crons.cron` for precise scheduling with cron expressions:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// convex/crons.ts
|
|
106
|
+
import { cronJobs } from "convex/server";
|
|
107
|
+
import { internal } from "./_generated/api";
|
|
108
|
+
|
|
109
|
+
const crons = cronJobs();
|
|
110
|
+
|
|
111
|
+
// Every day at 9 AM UTC
|
|
112
|
+
crons.cron(
|
|
113
|
+
"morning notifications",
|
|
114
|
+
"0 9 * * *",
|
|
115
|
+
internal.notifications.sendMorningDigest,
|
|
116
|
+
{}
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// Every Monday at 8 AM UTC
|
|
120
|
+
crons.cron(
|
|
121
|
+
"weekly summary",
|
|
122
|
+
"0 8 * * 1",
|
|
123
|
+
internal.reports.generateWeeklySummary,
|
|
124
|
+
{}
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// First day of every month at midnight
|
|
128
|
+
crons.cron(
|
|
129
|
+
"monthly billing",
|
|
130
|
+
"0 0 1 * *",
|
|
131
|
+
internal.billing.processMonthlyBilling,
|
|
132
|
+
{}
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// Every 15 minutes
|
|
136
|
+
crons.cron(
|
|
137
|
+
"frequent sync",
|
|
138
|
+
"*/15 * * * *",
|
|
139
|
+
internal.sync.syncData,
|
|
140
|
+
{}
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
export default crons;
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Cron Expression Reference
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
┌───────────── minute (0-59)
|
|
150
|
+
│ ┌───────────── hour (0-23)
|
|
151
|
+
│ │ ┌───────────── day of month (1-31)
|
|
152
|
+
│ │ │ ┌───────────── month (1-12)
|
|
153
|
+
│ │ │ │ ┌───────────── day of week (0-6, Sunday=0)
|
|
154
|
+
│ │ │ │ │
|
|
155
|
+
* * * * *
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Common patterns:
|
|
159
|
+
- `* * * * *` - Every minute
|
|
160
|
+
- `0 * * * *` - Every hour
|
|
161
|
+
- `0 0 * * *` - Every day at midnight
|
|
162
|
+
- `0 0 * * 0` - Every Sunday at midnight
|
|
163
|
+
- `0 0 1 * *` - First day of every month
|
|
164
|
+
- `*/5 * * * *` - Every 5 minutes
|
|
165
|
+
- `0 9-17 * * 1-5` - Every hour from 9 AM to 5 PM, Monday through Friday
|
|
166
|
+
|
|
167
|
+
### Internal Functions for Crons
|
|
168
|
+
|
|
169
|
+
Cron jobs should call internal functions for security:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
// convex/tasks.ts
|
|
173
|
+
import { internalMutation, internalQuery } from "./_generated/server";
|
|
174
|
+
import { v } from "convex/values";
|
|
175
|
+
|
|
176
|
+
// Cleanup expired sessions
|
|
177
|
+
export const cleanupExpiredSessions = internalMutation({
|
|
178
|
+
args: {},
|
|
179
|
+
returns: v.number(),
|
|
180
|
+
handler: async (ctx) => {
|
|
181
|
+
const oneHourAgo = Date.now() - 60 * 60 * 1000;
|
|
182
|
+
|
|
183
|
+
const expiredSessions = await ctx.db
|
|
184
|
+
.query("sessions")
|
|
185
|
+
.withIndex("by_lastActive")
|
|
186
|
+
.filter((q) => q.lt(q.field("lastActive"), oneHourAgo))
|
|
187
|
+
.collect();
|
|
188
|
+
|
|
189
|
+
for (const session of expiredSessions) {
|
|
190
|
+
await ctx.db.delete(session._id);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return expiredSessions.length;
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Process pending tasks
|
|
198
|
+
export const processPendingTasks = internalMutation({
|
|
199
|
+
args: {},
|
|
200
|
+
returns: v.null(),
|
|
201
|
+
handler: async (ctx) => {
|
|
202
|
+
const pendingTasks = await ctx.db
|
|
203
|
+
.query("tasks")
|
|
204
|
+
.withIndex("by_status", (q) => q.eq("status", "pending"))
|
|
205
|
+
.take(100);
|
|
206
|
+
|
|
207
|
+
for (const task of pendingTasks) {
|
|
208
|
+
await ctx.db.patch(task._id, {
|
|
209
|
+
status: "processing",
|
|
210
|
+
startedAt: Date.now(),
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Schedule the actual processing
|
|
214
|
+
await ctx.scheduler.runAfter(0, internal.tasks.processTask, {
|
|
215
|
+
taskId: task._id,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return null;
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Cron Jobs with Arguments
|
|
225
|
+
|
|
226
|
+
Pass static arguments to cron jobs:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
// convex/crons.ts
|
|
230
|
+
import { cronJobs } from "convex/server";
|
|
231
|
+
import { internal } from "./_generated/api";
|
|
232
|
+
|
|
233
|
+
const crons = cronJobs();
|
|
234
|
+
|
|
235
|
+
// Different cleanup intervals for different types
|
|
236
|
+
crons.interval(
|
|
237
|
+
"cleanup temp files",
|
|
238
|
+
{ hours: 1 },
|
|
239
|
+
internal.cleanup.cleanupByType,
|
|
240
|
+
{ fileType: "temp", maxAge: 3600000 }
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
crons.interval(
|
|
244
|
+
"cleanup cache files",
|
|
245
|
+
{ hours: 24 },
|
|
246
|
+
internal.cleanup.cleanupByType,
|
|
247
|
+
{ fileType: "cache", maxAge: 86400000 }
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
export default crons;
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// convex/cleanup.ts
|
|
255
|
+
import { internalMutation } from "./_generated/server";
|
|
256
|
+
import { v } from "convex/values";
|
|
257
|
+
|
|
258
|
+
export const cleanupByType = internalMutation({
|
|
259
|
+
args: {
|
|
260
|
+
fileType: v.string(),
|
|
261
|
+
maxAge: v.number(),
|
|
262
|
+
},
|
|
263
|
+
returns: v.number(),
|
|
264
|
+
handler: async (ctx, args) => {
|
|
265
|
+
const cutoff = Date.now() - args.maxAge;
|
|
266
|
+
|
|
267
|
+
const oldFiles = await ctx.db
|
|
268
|
+
.query("files")
|
|
269
|
+
.withIndex("by_type_and_created", (q) =>
|
|
270
|
+
q.eq("type", args.fileType).lt("createdAt", cutoff)
|
|
271
|
+
)
|
|
272
|
+
.collect();
|
|
273
|
+
|
|
274
|
+
for (const file of oldFiles) {
|
|
275
|
+
await ctx.storage.delete(file.storageId);
|
|
276
|
+
await ctx.db.delete(file._id);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return oldFiles.length;
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Monitoring and Logging
|
|
285
|
+
|
|
286
|
+
Add logging to track cron job execution:
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
// convex/tasks.ts
|
|
290
|
+
import { internalMutation } from "./_generated/server";
|
|
291
|
+
import { v } from "convex/values";
|
|
292
|
+
|
|
293
|
+
export const cleanupWithLogging = internalMutation({
|
|
294
|
+
args: {},
|
|
295
|
+
returns: v.null(),
|
|
296
|
+
handler: async (ctx) => {
|
|
297
|
+
const startTime = Date.now();
|
|
298
|
+
let processedCount = 0;
|
|
299
|
+
let errorCount = 0;
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
const expiredItems = await ctx.db
|
|
303
|
+
.query("items")
|
|
304
|
+
.withIndex("by_expiresAt")
|
|
305
|
+
.filter((q) => q.lt(q.field("expiresAt"), Date.now()))
|
|
306
|
+
.collect();
|
|
307
|
+
|
|
308
|
+
for (const item of expiredItems) {
|
|
309
|
+
try {
|
|
310
|
+
await ctx.db.delete(item._id);
|
|
311
|
+
processedCount++;
|
|
312
|
+
} catch (error) {
|
|
313
|
+
errorCount++;
|
|
314
|
+
console.error(`Failed to delete item ${item._id}:`, error);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Log job completion
|
|
319
|
+
await ctx.db.insert("cronLogs", {
|
|
320
|
+
jobName: "cleanup",
|
|
321
|
+
startTime,
|
|
322
|
+
endTime: Date.now(),
|
|
323
|
+
duration: Date.now() - startTime,
|
|
324
|
+
processedCount,
|
|
325
|
+
errorCount,
|
|
326
|
+
status: errorCount === 0 ? "success" : "partial",
|
|
327
|
+
});
|
|
328
|
+
} catch (error) {
|
|
329
|
+
// Log job failure
|
|
330
|
+
await ctx.db.insert("cronLogs", {
|
|
331
|
+
jobName: "cleanup",
|
|
332
|
+
startTime,
|
|
333
|
+
endTime: Date.now(),
|
|
334
|
+
duration: Date.now() - startTime,
|
|
335
|
+
processedCount,
|
|
336
|
+
errorCount,
|
|
337
|
+
status: "failed",
|
|
338
|
+
error: String(error),
|
|
339
|
+
});
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return null;
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Batching for Large Datasets
|
|
349
|
+
|
|
350
|
+
Handle large datasets in batches to avoid timeouts:
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
// convex/tasks.ts
|
|
354
|
+
import { internalMutation } from "./_generated/server";
|
|
355
|
+
import { internal } from "./_generated/api";
|
|
356
|
+
import { v } from "convex/values";
|
|
357
|
+
|
|
358
|
+
const BATCH_SIZE = 100;
|
|
359
|
+
|
|
360
|
+
export const processBatch = internalMutation({
|
|
361
|
+
args: {
|
|
362
|
+
cursor: v.optional(v.string()),
|
|
363
|
+
},
|
|
364
|
+
returns: v.null(),
|
|
365
|
+
handler: async (ctx, args) => {
|
|
366
|
+
const result = await ctx.db
|
|
367
|
+
.query("items")
|
|
368
|
+
.withIndex("by_status", (q) => q.eq("status", "pending"))
|
|
369
|
+
.paginate({ numItems: BATCH_SIZE, cursor: args.cursor ?? null });
|
|
370
|
+
|
|
371
|
+
for (const item of result.page) {
|
|
372
|
+
await ctx.db.patch(item._id, {
|
|
373
|
+
status: "processed",
|
|
374
|
+
processedAt: Date.now(),
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Schedule next batch if there are more items
|
|
379
|
+
if (!result.isDone) {
|
|
380
|
+
await ctx.scheduler.runAfter(0, internal.tasks.processBatch, {
|
|
381
|
+
cursor: result.continueCursor,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return null;
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### External API Calls in Crons
|
|
391
|
+
|
|
392
|
+
Use actions for external API calls:
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
// convex/sync.ts
|
|
396
|
+
"use node";
|
|
397
|
+
|
|
398
|
+
import { internalAction } from "./_generated/server";
|
|
399
|
+
import { internal } from "./_generated/api";
|
|
400
|
+
import { v } from "convex/values";
|
|
401
|
+
|
|
402
|
+
export const syncExternalData = internalAction({
|
|
403
|
+
args: {},
|
|
404
|
+
returns: v.null(),
|
|
405
|
+
handler: async (ctx) => {
|
|
406
|
+
// Fetch from external API
|
|
407
|
+
const response = await fetch("https://api.example.com/data", {
|
|
408
|
+
headers: {
|
|
409
|
+
Authorization: `Bearer ${process.env.API_KEY}`,
|
|
410
|
+
},
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
if (!response.ok) {
|
|
414
|
+
throw new Error(`API request failed: ${response.status}`);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const data = await response.json();
|
|
418
|
+
|
|
419
|
+
// Store the data using a mutation
|
|
420
|
+
await ctx.runMutation(internal.sync.storeExternalData, {
|
|
421
|
+
data,
|
|
422
|
+
syncedAt: Date.now(),
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
return null;
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
export const storeExternalData = internalMutation({
|
|
430
|
+
args: {
|
|
431
|
+
data: v.any(),
|
|
432
|
+
syncedAt: v.number(),
|
|
433
|
+
},
|
|
434
|
+
returns: v.null(),
|
|
435
|
+
handler: async (ctx, args) => {
|
|
436
|
+
await ctx.db.insert("externalData", {
|
|
437
|
+
data: args.data,
|
|
438
|
+
syncedAt: args.syncedAt,
|
|
439
|
+
});
|
|
440
|
+
return null;
|
|
441
|
+
},
|
|
442
|
+
});
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
// convex/crons.ts
|
|
447
|
+
import { cronJobs } from "convex/server";
|
|
448
|
+
import { internal } from "./_generated/api";
|
|
449
|
+
|
|
450
|
+
const crons = cronJobs();
|
|
451
|
+
|
|
452
|
+
crons.interval(
|
|
453
|
+
"sync external data",
|
|
454
|
+
{ minutes: 15 },
|
|
455
|
+
internal.sync.syncExternalData,
|
|
456
|
+
{}
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
export default crons;
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
## Examples
|
|
463
|
+
|
|
464
|
+
### Schema for Cron Job Logging
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
// convex/schema.ts
|
|
468
|
+
import { defineSchema, defineTable } from "convex/server";
|
|
469
|
+
import { v } from "convex/values";
|
|
470
|
+
|
|
471
|
+
export default defineSchema({
|
|
472
|
+
cronLogs: defineTable({
|
|
473
|
+
jobName: v.string(),
|
|
474
|
+
startTime: v.number(),
|
|
475
|
+
endTime: v.number(),
|
|
476
|
+
duration: v.number(),
|
|
477
|
+
processedCount: v.number(),
|
|
478
|
+
errorCount: v.number(),
|
|
479
|
+
status: v.union(
|
|
480
|
+
v.literal("success"),
|
|
481
|
+
v.literal("partial"),
|
|
482
|
+
v.literal("failed")
|
|
483
|
+
),
|
|
484
|
+
error: v.optional(v.string()),
|
|
485
|
+
})
|
|
486
|
+
.index("by_job", ["jobName"])
|
|
487
|
+
.index("by_status", ["status"])
|
|
488
|
+
.index("by_startTime", ["startTime"]),
|
|
489
|
+
|
|
490
|
+
sessions: defineTable({
|
|
491
|
+
userId: v.id("users"),
|
|
492
|
+
token: v.string(),
|
|
493
|
+
lastActive: v.number(),
|
|
494
|
+
expiresAt: v.number(),
|
|
495
|
+
})
|
|
496
|
+
.index("by_user", ["userId"])
|
|
497
|
+
.index("by_lastActive", ["lastActive"])
|
|
498
|
+
.index("by_expiresAt", ["expiresAt"]),
|
|
499
|
+
|
|
500
|
+
tasks: defineTable({
|
|
501
|
+
type: v.string(),
|
|
502
|
+
status: v.union(
|
|
503
|
+
v.literal("pending"),
|
|
504
|
+
v.literal("processing"),
|
|
505
|
+
v.literal("completed"),
|
|
506
|
+
v.literal("failed")
|
|
507
|
+
),
|
|
508
|
+
data: v.any(),
|
|
509
|
+
createdAt: v.number(),
|
|
510
|
+
startedAt: v.optional(v.number()),
|
|
511
|
+
completedAt: v.optional(v.number()),
|
|
512
|
+
})
|
|
513
|
+
.index("by_status", ["status"])
|
|
514
|
+
.index("by_type_and_status", ["type", "status"]),
|
|
515
|
+
});
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Complete Cron Configuration Example
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
// convex/crons.ts
|
|
522
|
+
import { cronJobs } from "convex/server";
|
|
523
|
+
import { internal } from "./_generated/api";
|
|
524
|
+
|
|
525
|
+
const crons = cronJobs();
|
|
526
|
+
|
|
527
|
+
// Cleanup jobs
|
|
528
|
+
crons.interval(
|
|
529
|
+
"cleanup expired sessions",
|
|
530
|
+
{ hours: 1 },
|
|
531
|
+
internal.cleanup.expiredSessions,
|
|
532
|
+
{}
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
crons.interval(
|
|
536
|
+
"cleanup old logs",
|
|
537
|
+
{ hours: 24 },
|
|
538
|
+
internal.cleanup.oldLogs,
|
|
539
|
+
{ maxAgeDays: 30 }
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
// Sync jobs
|
|
543
|
+
crons.interval(
|
|
544
|
+
"sync user data",
|
|
545
|
+
{ minutes: 15 },
|
|
546
|
+
internal.sync.userData,
|
|
547
|
+
{}
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
// Report jobs
|
|
551
|
+
crons.cron(
|
|
552
|
+
"daily analytics",
|
|
553
|
+
"0 1 * * *",
|
|
554
|
+
internal.reports.dailyAnalytics,
|
|
555
|
+
{}
|
|
556
|
+
);
|
|
557
|
+
|
|
558
|
+
crons.cron(
|
|
559
|
+
"weekly summary",
|
|
560
|
+
"0 9 * * 1",
|
|
561
|
+
internal.reports.weeklySummary,
|
|
562
|
+
{}
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
// Health checks
|
|
566
|
+
crons.interval(
|
|
567
|
+
"service health check",
|
|
568
|
+
{ minutes: 5 },
|
|
569
|
+
internal.monitoring.healthCheck,
|
|
570
|
+
{}
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
export default crons;
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
## Best Practices
|
|
577
|
+
|
|
578
|
+
- Never run `npx convex deploy` unless explicitly instructed
|
|
579
|
+
- Never run any git commands unless explicitly instructed
|
|
580
|
+
- Only use `crons.interval` or `crons.cron` methods, not deprecated helpers
|
|
581
|
+
- Always call internal functions from cron jobs for security
|
|
582
|
+
- Import `internal` from `_generated/api` even for functions in the same file
|
|
583
|
+
- Add logging and monitoring for production cron jobs
|
|
584
|
+
- Use batching for operations that process large datasets
|
|
585
|
+
- Handle errors gracefully to prevent job failures
|
|
586
|
+
- Use meaningful job names for dashboard visibility
|
|
587
|
+
- Consider timezone when using cron expressions (Convex uses UTC)
|
|
588
|
+
|
|
589
|
+
## Common Pitfalls
|
|
590
|
+
|
|
591
|
+
1. **Using public functions** - Cron jobs should call internal functions only
|
|
592
|
+
2. **Long-running mutations** - Break large operations into batches
|
|
593
|
+
3. **Missing error handling** - Unhandled errors will fail the entire job
|
|
594
|
+
4. **Forgetting timezone** - All cron expressions use UTC
|
|
595
|
+
5. **Using deprecated helpers** - Avoid `crons.hourly`, `crons.daily`, etc.
|
|
596
|
+
6. **Not logging execution** - Makes debugging production issues difficult
|
|
597
|
+
|
|
598
|
+
## References
|
|
599
|
+
|
|
600
|
+
- Convex Documentation: https://docs.convex.dev/
|
|
601
|
+
- Convex LLMs.txt: https://docs.convex.dev/llms.txt
|
|
602
|
+
- Cron Jobs: https://docs.convex.dev/scheduling/cron-jobs
|
|
603
|
+
- Scheduling Overview: https://docs.convex.dev/scheduling
|
|
604
|
+
- Scheduled Functions: https://docs.convex.dev/scheduling/scheduled-functions
|
|
Binary file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g clip-path="url(#clip0_3_23)">
|
|
3
|
+
<g clip-path="url(#clip1_3_23)">
|
|
4
|
+
<path d="M10.0643 12.5735C12.3769 12.3166 14.5572 11.0843 15.7577 9.02756C15.1892 14.1148 9.62646 17.3302 5.08583 15.356C4.66743 15.1746 4.30728 14.8728 4.06013 14.4848C3.03973 12.8825 2.7043 10.8437 3.18626 8.99344C4.56327 11.37 7.3632 12.8267 10.0643 12.5735Z" fill="#F3B01C"/>
|
|
5
|
+
<path d="M3.1018 7.50072C2.16436 9.66714 2.12376 12.2034 3.27303 14.2907C-0.771507 11.2479 -0.72737 4.7362 3.2236 1.72378C3.58904 1.44535 4.02333 1.2801 4.47881 1.25494C6.3519 1.15614 8.25501 1.88006 9.58963 3.22909C6.87799 3.25604 4.23695 4.99308 3.1018 7.50072Z" fill="#8D2676"/>
|
|
6
|
+
<path d="M10.8974 3.89562C9.52924 1.98794 7.38779 0.68921 5.04156 0.649695C9.57686 -1.40888 15.1555 1.92867 15.7629 6.86314C15.8194 7.32119 15.7452 7.78824 15.5421 8.20138C14.6948 9.92223 13.1236 11.2569 11.2876 11.7508C12.6328 9.25579 12.4668 6.20748 10.8974 3.89562Z" fill="#EE342F"/>
|
|
7
|
+
</g>
|
|
8
|
+
</g>
|
|
9
|
+
<defs>
|
|
10
|
+
<clipPath id="clip0_3_23">
|
|
11
|
+
<rect width="16" height="16" fill="white"/>
|
|
12
|
+
</clipPath>
|
|
13
|
+
<clipPath id="clip1_3_23">
|
|
14
|
+
<rect width="16" height="16" fill="white"/>
|
|
15
|
+
</clipPath>
|
|
16
|
+
</defs>
|
|
17
|
+
</svg>
|