fix(build+worker): restore seedDatabase export + DISABLE_WORKER_CONSUMER guard
Browse files1. packages/database/seed.ts: restore re-export of seedDatabase.
moduleResolution:node ignores package.json exports field β TypeScript
resolves @repo /database/seed to root seed.ts. Without the re-export,
tsc sees no seedDatabase export β TS2339 build failure.
Runtime still uses dist/src/seed.js (via exports field) which is
compiled CJS with no TypeScript syntax.
2. apps/whatsapp-worker/src/index.ts: add DISABLE_WORKER_CONSUMER guard.
Both Railway and HuggingFace workers share the same BullMQ/Redis queue.
When HuggingFace wins a send-message job, it fails with SSL EPROTO
(HF cannot reach graph.facebook.com). Setting DISABLE_WORKER_CONSUMER=true
on HuggingFace keeps the bridge running (accepts forwarded webhooks) but
stops it from consuming jobs β only Railway's worker sends to Meta.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- apps/whatsapp-worker/src/index.ts +31 -22
- packages/database/seed.ts +3 -0
|
@@ -171,7 +171,14 @@ server.post('/v1/internal/whatsapp/inbound', async (req: FastifyRequest, reply:
|
|
| 171 |
server.get('/health', async () => ({ status: 'ok' }));
|
| 172 |
|
| 173 |
// βββ WORKER ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 174 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
const organizationId = job.data.organizationId;
|
| 176 |
if (!organizationId) {
|
| 177 |
logger.error({ jobId: job.id, jobName: job.name }, '[WORKER] Job missing organizationId β skipping to prevent cross-tenant contamination');
|
|
@@ -213,20 +220,21 @@ const worker = new Worker('whatsapp-queue', async (job: Job<JobData>) => {
|
|
| 213 |
throw err;
|
| 214 |
}
|
| 215 |
});
|
| 216 |
-
}, {
|
| 217 |
connection,
|
| 218 |
concurrency: parseInt(process.env.WORKER_CONCURRENCY || '5')
|
| 219 |
-
});
|
| 220 |
-
|
| 221 |
-
worker.on('completed', job => {
|
| 222 |
-
logger.info(`[WORKER] Job ${job.id} has completed!`);
|
| 223 |
-
});
|
| 224 |
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
|
| 229 |
-
const notificationWorker = new Worker('notification-queue', async (job: Job<any>) => {
|
| 230 |
logger.info(`[NOTIFICATION_WORKER] Processing job: ${job.name} (${job.id})`);
|
| 231 |
try {
|
| 232 |
const handler = handlers[job.name];
|
|
@@ -239,18 +247,19 @@ const notificationWorker = new Worker('notification-queue', async (job: Job<any>
|
|
| 239 |
logger.error(`[NOTIFICATION_WORKER] Job ${job.id} failed:`, err);
|
| 240 |
throw err;
|
| 241 |
}
|
| 242 |
-
}, {
|
| 243 |
connection,
|
| 244 |
concurrency: 2
|
| 245 |
-
});
|
| 246 |
-
|
| 247 |
-
notificationWorker.on('completed', job => {
|
| 248 |
-
logger.info(`[NOTIFICATION_WORKER] Job ${job.id} has completed!`);
|
| 249 |
-
});
|
| 250 |
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
|
| 255 |
// βββ STARTUP βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 256 |
const PORT = 8082; // Internal port for the bridge, avoids conflict with the main API on Railway's public PORT
|
|
@@ -279,8 +288,8 @@ const handleShutdown = async (signal: string) => {
|
|
| 279 |
logger.info('[SHUTDOWN] HTTP Bridge server closed.');
|
| 280 |
|
| 281 |
// 2. Close workers (stop processing new jobs)
|
| 282 |
-
await worker.close();
|
| 283 |
-
await notificationWorker.close();
|
| 284 |
logger.info('[SHUTDOWN] Workers closed.');
|
| 285 |
|
| 286 |
// 3. Close queues and Redis
|
|
|
|
| 171 |
server.get('/health', async () => ({ status: 'ok' }));
|
| 172 |
|
| 173 |
// βββ WORKER ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 174 |
+
// Set DISABLE_WORKER_CONSUMER=true on HuggingFace to prevent it from competing
|
| 175 |
+
// with the Railway worker on the shared BullMQ queue (HF can't reach graph.facebook.com).
|
| 176 |
+
const CONSUMER_ENABLED = process.env.DISABLE_WORKER_CONSUMER !== 'true';
|
| 177 |
+
if (!CONSUMER_ENABLED) {
|
| 178 |
+
logger.warn('[WORKER] DISABLE_WORKER_CONSUMER=true β BullMQ consumer disabled. Bridge only mode.');
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
const worker = CONSUMER_ENABLED ? new Worker('whatsapp-queue', async (job: Job<JobData>) => {
|
| 182 |
const organizationId = job.data.organizationId;
|
| 183 |
if (!organizationId) {
|
| 184 |
logger.error({ jobId: job.id, jobName: job.name }, '[WORKER] Job missing organizationId β skipping to prevent cross-tenant contamination');
|
|
|
|
| 220 |
throw err;
|
| 221 |
}
|
| 222 |
});
|
| 223 |
+
}, {
|
| 224 |
connection,
|
| 225 |
concurrency: parseInt(process.env.WORKER_CONCURRENCY || '5')
|
| 226 |
+
}) : null;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
|
| 228 |
+
if (worker) {
|
| 229 |
+
worker.on('completed', job => {
|
| 230 |
+
logger.info(`[WORKER] Job ${job.id} has completed!`);
|
| 231 |
+
});
|
| 232 |
+
worker.on('failed', (job, err) => {
|
| 233 |
+
logger.error(`[WORKER] Job ${job?.id} failed: ${err.message}`);
|
| 234 |
+
});
|
| 235 |
+
}
|
| 236 |
|
| 237 |
+
const notificationWorker = CONSUMER_ENABLED ? new Worker('notification-queue', async (job: Job<any>) => {
|
| 238 |
logger.info(`[NOTIFICATION_WORKER] Processing job: ${job.name} (${job.id})`);
|
| 239 |
try {
|
| 240 |
const handler = handlers[job.name];
|
|
|
|
| 247 |
logger.error(`[NOTIFICATION_WORKER] Job ${job.id} failed:`, err);
|
| 248 |
throw err;
|
| 249 |
}
|
| 250 |
+
}, {
|
| 251 |
connection,
|
| 252 |
concurrency: 2
|
| 253 |
+
}) : null;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
|
| 255 |
+
if (notificationWorker) {
|
| 256 |
+
notificationWorker.on('completed', job => {
|
| 257 |
+
logger.info(`[NOTIFICATION_WORKER] Job ${job.id} has completed!`);
|
| 258 |
+
});
|
| 259 |
+
notificationWorker.on('failed', (job, err) => {
|
| 260 |
+
logger.error(`[NOTIFICATION_WORKER] Job ${job?.id} failed: ${err?.message}`);
|
| 261 |
+
});
|
| 262 |
+
}
|
| 263 |
|
| 264 |
// βββ STARTUP βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 265 |
const PORT = 8082; // Internal port for the bridge, avoids conflict with the main API on Railway's public PORT
|
|
|
|
| 288 |
logger.info('[SHUTDOWN] HTTP Bridge server closed.');
|
| 289 |
|
| 290 |
// 2. Close workers (stop processing new jobs)
|
| 291 |
+
await worker?.close();
|
| 292 |
+
await notificationWorker?.close();
|
| 293 |
logger.info('[SHUTDOWN] Workers closed.');
|
| 294 |
|
| 295 |
// 3. Close queues and Redis
|
|
@@ -1,3 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
| 1 |
/**
|
| 2 |
* Seed Script β Module 1 : Eco-Moto Business
|
| 3 |
* Inserts a realistic Track + 3 TrackDays for testing.
|
|
|
|
| 1 |
+
// Re-export for @repo/database/seed import in CommandHandler (moduleResolution:node ignores exports field)
|
| 2 |
+
export { seedDatabase } from './src/seed';
|
| 3 |
+
|
| 4 |
/**
|
| 5 |
* Seed Script β Module 1 : Eco-Moto Business
|
| 6 |
* Inserts a realistic Track + 3 TrackDays for testing.
|