-
-
Notifications
You must be signed in to change notification settings - Fork 8
Make report generation asynchronous using a job queue #663
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
bb1db2e
536fdc7
5e20729
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import { handleCallback } from '@vercel/queue'; | ||
| import { generateReportContext } from '@/lib/actions/chat'; | ||
| import { updateJob, getJob } from '@/lib/actions/jobs'; | ||
| import { NextRequest } from 'next/server'; | ||
|
|
||
| const queueHandler = handleCallback(async (payload: any) => { | ||
| const { jobId } = payload; | ||
| if (!jobId) { | ||
| console.error('No jobId in queue payload'); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| const job = await getJob(jobId); | ||
| if (!job) { | ||
| console.error(`Job ${jobId} not found`); | ||
| return; | ||
| } | ||
|
|
||
| await updateJob(jobId, { status: 'processing' }); | ||
|
|
||
| const { messages } = job.payload as { messages: any[] }; | ||
| const result = await generateReportContext(messages); | ||
|
|
||
| await updateJob(jobId, { | ||
| status: 'completed', | ||
| result, | ||
| updatedAt: new Date(), | ||
| }); | ||
| console.log(`Job ${jobId} completed via Vercel Queue`); | ||
| } catch (error) { | ||
| console.error(`Job ${jobId} failed in Vercel Queue:`, error); | ||
| await updateJob(jobId, { | ||
| status: 'failed', | ||
| error: error instanceof Error ? error.message : String(error), | ||
| updatedAt: new Date(), | ||
| }); | ||
| } | ||
| }); | ||
|
Comment on lines
+1
to
+39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify route structure and Redis integration
# Check for the expected route path
fd -t f 'route.ts' app/api/queues/
# Search for Upstash Redis setup and usage
rg -n 'Redis|UPSTASH' --type ts --type env -C2
# Check vercel.json for queue configuration
cat vercel.json 2>/dev/null || echo "vercel.json not found"Repository: QueueLab/QCX Length of output: 227 🏁 Script executed: #!/bin/bash
set -euo pipefail
# 1) Locate the queue callback route implementation(s)
echo "== handleCallback routes =="
rg -n "handleCallback" -S --glob "**/*.ts" --glob "**/*.tsx" . || true
# 2) Inspect app/api tree relevant to queue/generate-report
echo
echo "== List api route directories (shallow) =="
ls -la app/api 2>/dev/null || true
find app/api -maxdepth 3 -type f -name "route.ts" 2>/dev/null | sed 's|^\./||' | sort || true
# 3) Read the provided route file if it exists
echo
echo "== app/api/queue/route.ts (first 120 lines) =="
if [ -f "app/api/queue/route.ts" ]; then
sed -n '1,120p' app/api/queue/route.ts
else
echo "MISSING: app/api/queue/route.ts"
fi
# 4) Inspect jobs helpers for updatedAt behavior
echo
echo "== updateJob/getJob implementations =="
rg -n "function updateJob|export (async )?function updateJob|updateJob\\(" app/lib/actions/jobs.ts app/lib/actions/jobs.* app/lib/actions -S 2>/dev/null || true
for f in $(fd -t f "jobs.ts" app/lib/actions 2>/dev/null || true); do
echo
echo "---- $f (first 200 lines) ----"
sed -n '1,200p' "$f"
done
# 5) Search for Upstash Redis usage and env vars
echo
echo "== Upstash/Redis usage =="
rg -n "UPSTASH|Upstash|upstash|Redis" -S --glob "**/*.ts" --glob "**/*.js" --glob "**/*.tsx" --glob "**/*.env*" --glob "vercel.json" . || true
# 6) Check queue-related vercel config (if present)
echo
echo "== vercel.json (contents if exists) =="
if [ -f "vercel.json" ]; then
cat vercel.json
else
echo "MISSING: vercel.json"
fi
# 7) Check other queue/route paths possibly matching the PR summary expectation
echo
echo "== Look for generate-report routes =="
find app/api -type f -name "route.ts" 2>/dev/null | xargs -r -n 1 basename | head -n 200 || true
rg -n "generate-report|generateReport|report" app/api -S --glob "**/*.ts" --glob "**/*.tsx" || trueRepository: QueueLab/QCX Length of output: 2420 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate jobs helper files
echo "== Locate jobs helper(s) =="
fd -t f -i "*jobs*.ts" app/lib || true
fd -t f -i "jobs.ts" app/lib || true
fd -t f -i "*job*.ts" app/lib/actions || true
# Read likely jobs files
for f in $(fd -t f -i "*jobs*.ts" app/lib 2>/dev/null); do
echo
echo "---- $f (first 200 lines) ----"
sed -n '1,200p' "$f"
done
# Search for upstash/redis clients (beyond env mentions)
echo
echo "== Redis/Upstash client code usage =="
rg -n "(upstash|UPSTASH|`@upstash`\/redis|Redis|createClient|redis\.)" -S --glob "**/*.ts" --glob "**/*.tsx" app lib src . || true
# Ensure we understand queue callback retry behavior usage in codebase
echo
echo "== Other `@vercel/queue` usage patterns =="
rg -n "`@vercel/queue`|handleCallback" -S --glob "**/*.ts" . || trueRepository: QueueLab/QCX Length of output: 791 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Find updateJob/getJob definitions =="
rg -n "updateJob\\b|getJob\\b" -S --glob "**/*.ts" --glob "**/*.tsx" . || true
echo
echo "== Find generateReportContext definition =="
rg -n "generateReportContext\\b" -S --glob "**/*.ts" --glob "**/*.tsx" . || true
echo
echo "== List files named jobs.ts / actions/jobs =="
fd -t f -i "jobs.ts" . || true
fd -t f -i "jobs" . || true
echo
echo "== Inspect any found jobs-related file(s) (first 200 lines) =="
# Take a limited set of likely matches to avoid huge output
for f in $(rg -l "export (async )?function updateJob|export (async )?function getJob|updateJob\\(|getJob\\(" -S --glob "**/*.ts" . | head -n 20); do
echo
echo "---- $f (first 200 lines) ----"
sed -n '1,200p' "$f"
done
echo
echo "== Redis/Upstash code usage (broader search) =="
rg -n "(upstash|UPSTASH|`@upstash/redis`|upstash_redis|redis)" -S --glob "**/*.ts" --glob "**/*.tsx" . || true
echo
echo "== Find vercel/queue config docs/examples usage =="
rg -n "handleCallback|`@vercel/queue`" -S --glob "**/*.ts" --glob "**/*.tsx" . || true
echo
echo "== Locate any queue payload fields or TTL write logic =="
rg -n "(TTL|ttl|expire|expiration|setex|hset|write.*redis|redis.*set)" -S --glob "**/*.ts" --glob "**/*.tsx" . || trueRepository: QueueLab/QCX Length of output: 4958 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== generateReportContext implementation =="
sed -n '1,200p' lib/actions/chat.ts
echo
echo "== generateReportContext (tail if longer) =="
sed -n '200,400p' lib/actions/chat.ts 2>/dev/null || trueRepository: QueueLab/QCX Length of output: 7912 🌐 Web query:
💡 Result: In Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Search for enqueueJob/send usage =="
rg -n "enqueueJob\\b|`@vercel/queue`\\b|send\\b\\(" -S --glob "**/*.ts" --glob "**/*.tsx" . || true
echo
echo "== Inspect app/actions.tsx around Vercel Queue send =="
if [ -f "app/actions.tsx" ]; then
nl -ba app/actions.tsx | sed -n '1,120p'
nl -ba app/actions.tsx | sed -n '120,240p'
else
echo "MISSING: app/actions.tsx"
fi
echo
echo "== Search for generate-report / queues route strings =="
rg -n "(generate-report|generateReport|/api/queues|api/queues|queues/)" -S --glob "**/*.ts" --glob "**/*.tsx" . || true
echo
echo "== Inspect jobs table schema =="
rg -n "export const jobs\\b|jobs\\b.*schema|updatedAt|result|error" -S --glob "**/*.ts" --glob "**/*.tsx" lib/app src . || true
# Print jobs schema file if we can locate it
for f in $(rg -l "export const jobs\\b" . || true); do
echo
echo "---- $f (first 200 lines) ----"
sed -n '1,200p' "$f"
doneRepository: QueueLab/QCX Length of output: 699 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Inspect app/actions.tsx around Vercel Queue send =="
if [ -f "app/actions.tsx" ]; then
sed -n '1,140p' app/actions.tsx
else
echo "MISSING: app/actions.tsx"
fi
echo
echo "== Inspect app/actions.tsx near enqueueJob/send call =="
if [ -f "app/actions.tsx" ]; then
rg -n "enqueueJob|send\\(" app/actions.tsx
fi
echo
echo "== Search for generate-report / queues route strings =="
rg -n "(generate-report|generateReport|/api/queues|api/queues|queues/)" -S --glob "**/*.ts" --glob "**/*.tsx" . || true
echo
echo "== Locate jobs table schema (drizzle) =="
rg -n "jobs\\s*=\\s*|export const jobs\\b|updatedAt\\b|result\\b|error\\b" -S --glob "**/*.ts" --glob "**/*.tsx" lib . || true
echo
echo "== Print any file containing 'export const jobs' =="
for f in $(rg -l "export const jobs\\b" lib . 2>/dev/null || true); do
echo
echo "---- $f (first 220 lines) ----"
sed -n '1,220p' "$f"
doneRepository: QueueLab/QCX Length of output: 50368 Critical: Vercel Queue worker swallows failures, so jobs won’t retry
Also, the earlier “wrong route path” and “Upstash Redis storage” assertions don’t match this codebase: the producer uses 🤖 Prompt for AI Agents |
||
|
|
||
| export async function POST(request: NextRequest) { | ||
| return queueHandler(request); | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ import { AI } from '@/app/actions' | |
| import { useActions } from 'ai/rsc' | ||
| import { toast } from 'sonner' | ||
| import { ReportTemplate } from './report-template' | ||
| import { getJobStatus } from '@/lib/actions/jobs-client' | ||
| import { createPortal } from 'react-dom' | ||
|
|
||
| export const DownloadReportButton = () => { | ||
|
|
@@ -57,7 +58,34 @@ export const DownloadReportButton = () => { | |
| formData.append('action', 'generate_report_context'); | ||
| formData.append('messages', JSON.stringify(aiState.messages)); | ||
|
|
||
| const { title, summary } = await (actions as any).submit(formData); | ||
| const { jobId } = await (actions as any).submit(formData); | ||
|
|
||
| if (!jobId) { | ||
| throw new Error('Failed to start report generation job'); | ||
| } | ||
|
|
||
| // Polling for job completion | ||
| let jobResult = null; | ||
| let attempts = 0; | ||
| const maxAttempts = 60; // 60 seconds | ||
|
|
||
| while (attempts < maxAttempts) { | ||
| const { status, result, error } = await getJobStatus(jobId); | ||
| if (status === 'completed') { | ||
| jobResult = result; | ||
| break; | ||
| } else if (status === 'failed') { | ||
| throw new Error(error || 'Job failed'); | ||
| } | ||
| attempts++; | ||
| await new Promise(resolve => setTimeout(resolve, 1000)); | ||
| } | ||
|
|
||
| if (!jobResult) { | ||
| throw new Error('Report generation timed out'); | ||
| } | ||
|
|
||
| const { title, summary } = jobResult as { title: string, summary: string }; | ||
|
Comment on lines
+61
to
+88
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Polling implementation has multiple critical issues.
🤖 Prompt for AI Agents |
||
|
|
||
| const finalTitle = title || 'QCX Intelligence Analysis' | ||
| setReportTitle(finalTitle) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| 'use server' | ||
|
|
||
| import { getJobStatus as getJobStatusServer } from './jobs' | ||
|
|
||
| export async function getJobStatus(jobId: string) { | ||
| return await getJobStatusServer(jobId) | ||
| } | ||
|
Comment on lines
+1
to
+7
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: This file should have been removed or replaced. Per the PR summary and bot migration plan:
The current implementation is an unnecessary wrapper that imports 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| 'use server' | ||
|
|
||
| import { db } from '@/lib/db' | ||
| import { jobs } from '@/lib/db/schema' | ||
| import { eq } from 'drizzle-orm' | ||
| import { revalidatePath } from 'next/cache' | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win Remove unused import.
🤖 Prompt for AI Agents |
||
|
|
||
| export async function enqueueJob(userId: string, type: string, payload: any) { | ||
| try { | ||
| const result = await db.insert(jobs).values({ | ||
| userId, | ||
| type, | ||
| payload, | ||
| status: 'pending', | ||
| }).returning({ id: jobs.id }) | ||
|
|
||
| return result[0].id | ||
| } catch (error) { | ||
| console.error('Error enqueuing job:', error) | ||
| throw error | ||
| } | ||
| } | ||
|
|
||
| export async function getJob(jobId: string) { | ||
| try { | ||
| const result = await db.select().from(jobs).where(eq(jobs.id, jobId)).limit(1) | ||
| return result[0] || null | ||
| } catch (error) { | ||
| console.error('Error getting job:', error) | ||
| return null | ||
| } | ||
| } | ||
|
Comment on lines
+8
to
+32
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win Inconsistent error handling across CRUD functions.
Consider a unified strategy: either propagate all errors or return Result<T, Error> types consistently. 🤖 Prompt for AI Agents |
||
|
|
||
| export async function updateJob(jobId: string, updates: Partial<typeof jobs.$inferInsert>) { | ||
| try { | ||
| await db.update(jobs) | ||
| .set({ ...updates, updatedAt: new Date() }) | ||
| .where(eq(jobs.id, jobId)) | ||
| } catch (error) { | ||
| console.error('Error updating job:', error) | ||
| throw error | ||
| } | ||
| } | ||
|
Comment on lines
+34
to
+43
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win Type mismatch: Line 37 manually sets Prefer a dedicated update shape type or explicitly omit non-updatable fields: type JobUpdate = Partial<Pick<typeof jobs.$inferInsert, 'status' | 'result' | 'error' | 'payload'>>
export async function updateJob(jobId: string, updates: JobUpdate) {
// ...
}🤖 Prompt for AI Agents |
||
|
|
||
| export async function getJobStatus(jobId: string) { | ||
| 'use server' | ||
|
Comment on lines
+45
to
+46
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win Remove redundant Line 1 already declares 🤖 Prompt for AI Agents |
||
| const job = await getJob(jobId) | ||
| if (!job) return { status: 'not_found' } | ||
| return { | ||
| status: job.status, | ||
| result: job.result, | ||
| error: job.error | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -102,6 +102,18 @@ export const calendarNotes = pgTable('calendar_notes', { | |
| updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), | ||
| }); | ||
|
|
||
| export const jobs = pgTable('jobs', { | ||
| id: uuid('id').primaryKey().defaultRandom(), | ||
| userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), | ||
| type: text('type').notNull(), | ||
| status: text('status').notNull().default('pending'), | ||
| payload: jsonb('payload'), | ||
| result: jsonb('result'), | ||
| error: text('error'), | ||
| createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), | ||
| updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), | ||
| }); | ||
|
Comment on lines
+105
to
+115
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check for Upstash Redis usage and migration to drop jobs table
# Search for Upstash Redis imports/usage
rg -n '`@upstash/redis`' --type ts -C2
# Search for migration files that drop jobs table
fd -e sql -e ts 'migrat' --exec rg -l 'DROP TABLE.*jobs'
# Verify if jobs table is referenced in queue handler
rg -n "db.*jobs|from.*jobs|jobs\\..*" app/api/queue/route.ts app/api/queues/ -A2Repository: QueueLab/QCX Length of output: 323 🏁 Script executed: #!/bin/bash
set -euo pipefail
# 1) Upstash Redis usage
echo "=== Upstash Redis imports/usage ==="
rg -n "`@upstash/redis`" --type ts --type js -S . || true
# 2) Where migrations live, and whether any drop jobs table exists
echo
echo "=== Migration files mentioning jobs table drops ==="
# Try common migration dirs/patterns without assuming structure
for dir in ./.; do
fd -t f -e sql -e ts -e js -e mjs -e cjs -e kys -e drizzle -e knex . \
--hidden --exclude-dir node_modules --exclude-dir dist --exclude-dir build \
| rg -n "migrat|migration|drizzle|knex|sequelize|schema" -S -g'*.{sql,ts,js,mjs,cjs}' \
| head -n 2000 >/tmp/migration_candidates.txt || true
if [ -s /tmp/migration_candidates.txt ]; then
xargs -r rg -n --no-heading -S "DROP TABLE.*jobs|drop table.*jobs|DROP\\s+TABLE\\s+jobs|alter table.*jobs" \
< /tmp/migration_candidates.txt || true
else
echo "No migration candidate files found by heuristic."
fi
done
# 3) References to the jobs table/schema
echo
echo "=== References to jobs schema/table ==="
rg -n "lib/db/schema|schema\\.ts|\\bjobs\\b" --type ts --type js -S lib app || true
# 4) Check queue handlers for job persistence
echo
echo "=== Queue handler references to jobs/db ==="
rg -n "handleCallback|queue|jobs|updateJob|getJob|jobs\\." --type ts -S app lib || true
# 5) Show the exact schema.ts around the jobs definition for context
echo
echo "=== lib/db/schema.ts around jobs definition ==="
if [ -f "lib/db/schema.ts" ]; then
nl -ba lib/db/schema.ts | sed -n '80,140p'
else
echo "lib/db/schema.ts not found"
fiRepository: QueueLab/QCX Length of output: 4933 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Search: `@upstash/redis` and upstash usage ==="
rg -n "`@upstash/redis`|`@upstash`|upstash" --type ts --type js --hidden -S . || true
echo
rg -n "UPSTASH|UPSTASH_REDIS|REDIS_URL|REDIS" --hidden -S . || true
echo
echo "=== Search: queue + DB jobs persistence ==="
rg -n "handleCallback|`@vercel/queue`|enqueueJob|updateJob|getJob" --type ts --type js -S app lib || true
echo
echo "=== Migration directories/files discovery ==="
# List likely migration-related files/dirs without assuming structure
rg -n "drizzle|migration" --hidden -S . --glob '!**/node_modules/**' --glob '!**/dist/**' || true
# Find candidate SQL/TS migration files by name (heuristic)
echo
echo "=== Candidate migration files (by filename) ==="
find . -maxdepth 6 -type f \
\( -iname "*migration*" -o -iname "*migrations*" -o -iname "*drizzle*" -o -iname "*schema*" -o -iname "*ops*" \) \
\( -iname "*.sql" -o -iname "*.ts" -o -iname "*.js" -o -iname "*.mjs" \) 2>/dev/null | head -n 200 || true
echo
echo "=== Search migrations for jobs table drop ==="
# Search SQL/TS/JS for drop/alter statements on jobs (heuristic)
rg -n "DROP\\s+TABLE\\s+jobs|drop\\s+table\\s+jobs|ALTER\\s+TABLE\\s+jobs|alter\\s+table\\s+jobs" --hidden -S . \
--glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' --glob '!**/.next/**' || true
echo
echo "=== Show lib/db/schema.ts jobs section (lines ~100-140) ==="
if [ -f lib/db/schema.ts ]; then
sed -n '95,145p' lib/db/schema.ts
else
echo "lib/db/schema.ts not found"
fiRepository: QueueLab/QCX Length of output: 11180 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Directory listing: drizzle/* ==="
ls -la drizzle 2>/dev/null || true
ls -la drizzle/migrations 2>/dev/null || true
find drizzle -maxdepth 2 -type f 2>/dev/null | head -n 200 || true
echo
echo "=== Search drizzle/migrations for jobs table changes ==="
rg -n "DROP\\s+TABLE\\s+jobs|drop\\s+table\\s+jobs|ALTER\\s+TABLE\\s+jobs|alter\\s+table\\s+jobs|jobs\\b" -S drizzle/migrations 2>/dev/null || true
echo
echo "=== Inspect lib/actions/jobs.ts ==="
if [ -f lib/actions/jobs.ts ]; then
sed -n '1,120p' lib/actions/jobs.ts
else
echo "lib/actions/jobs.ts not found"
fi
echo
echo "=== Inspect app/api/queue/route.ts ==="
if [ -f app/api/queue/route.ts ]; then
sed -n '1,120p' app/api/queue/route.ts
else
echo "app/api/queue/route.ts not found"
fi
echo
echo "=== Status values used in queue handler code ==="
rg -n "updateJob\\(.*status\\s*:\\s*'|status\\s*:\\s*'|status:\\s*\"|set\\(\"status\"|processing|pending|completed|failed|error" -S app lib
echo
echo "=== Queries filtering by jobs.userId or jobs.status ==="
rg -n "from\\(jobs\\)|\\.select\\(\\).*jobs|where\\(eq\\(jobs\\.(userId|status)" -S lib appRepository: QueueLab/QCX Length of output: 25508 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Show drizzle/migrations/*.sql files headers and any jobs-related statements ==="
for f in drizzle/migrations/*.sql; do
echo
echo "--- $f ---"
# Print lines around any mention of jobs
rg -n "\\bjobs\\b" "$f" -n || true
# Print full file if small-ish, else only relevant ranges
lines=$(wc -l < "$f")
echo "Lines: $lines"
if [ "$lines" -le 200 ]; then
cat "$f"
else
# best-effort: show sections near jobs keywords
rg -n -C3 "\\bjobs\\b" "$f" || true
fi
done
echo
echo "=== Search for DROP/ALTER jobs in the whole migrations folder ==="
rg -n "DROP\\s+TABLE\\s+jobs|drop\\s+table\\s+jobs|ALTER\\s+TABLE\\s+jobs|alter\\s+table\\s+jobs" drizzle/migrations || true
echo
echo "=== Check drizzle.config.ts and migrate.ts migrationsFolder setting ==="
sed -n '1,120p' drizzle.config.ts
sed -n '1,120p' lib/db/migrate.tsRepository: QueueLab/QCX Length of output: 5253 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Search for indexes/constraints on jobs in lib/db/schema.ts ==="
rg -n "index\\(|unique\\(|check\\(|constraint\\(|enum\\(" lib/db/schema.ts -S || true
echo
echo "=== Show jobs table block again with nearby lines (for context) ==="
sed -n '90,140p' lib/db/schema.tsRepository: QueueLab/QCX Length of output: 2259 Critical: Queue worker still persists into Postgres
🤖 Prompt for AI Agents |
||
|
|
||
| // Relations | ||
| export const usersRelations = relations(users, ({ many }) => ({ | ||
| chats: many(chats), | ||
|
|
@@ -111,6 +123,14 @@ export const usersRelations = relations(users, ({ many }) => ({ | |
| systemPrompts: many(systemPrompts), | ||
| locations: many(locations), | ||
| visualizations: many(visualizations), | ||
| jobs: many(jobs), | ||
| })); | ||
|
Comment on lines
+126
to
+127
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove relations for jobs table. Since the Also applies to: 129-134 🤖 Prompt for AI Agents |
||
|
|
||
| export const jobsRelations = relations(jobs, ({ one }) => ({ | ||
| user: one(users, { | ||
| fields: [jobs.userId], | ||
| references: [users.id], | ||
| }), | ||
| })); | ||
|
|
||
| export const chatsRelations = relations(chats, ({ one, many }) => ({ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Early returns prevent Vercel Queue from retrying failures.
Lines 9 and 16 return early on missing
jobIdor job not found. Per Vercel Queue documentation, the callback should throw an error to trigger retries. Returning without throwing signals success, and the message is removed from the queue.If
jobIdis missing or the job is not found, these are likely transient or poison-message issues. For poison messages, you should throw to let Vercel Queue apply retry/DLQ logic. For transient DB failures (job not found due to replication lag), throwing enables retry.🔁 Recommended fix: throw instead of return
const { jobId } = payload; if (!jobId) { console.error('No jobId in queue payload'); - return; + throw new Error('No jobId in queue payload'); } // ... const job = await getJob(jobId); if (!job) { console.error(`Job ${jobId} not found`); - return; + throw new Error(`Job ${jobId} not found`); }Also applies to: 14-17
🤖 Prompt for AI Agents