| 40-job-infrastructure |
02 |
content-jobs |
| http-routes |
| sse |
| async-jobs |
| live-events |
| integration-tests |
|
| requires |
provides |
affects |
| content_jobs DB table (40-01) |
| contentJobStore CRUD service (40-01) |
| contentJobRunner async dispatcher (40-01) |
| subscribeCompanyLiveEvents from live-events.ts |
| assertCompanyAccess from authz.ts |
|
| POST /api/companies/:companyId/content-jobs (202 job submission) |
| GET /api/companies/:companyId/content-jobs (list) |
| GET /api/companies/:companyId/content-jobs/:jobId (single job) |
| GET /api/companies/:companyId/content-jobs/:jobId/events (SSE progress stream) |
|
| server/src/routes/content-jobs.ts |
| server/src/app.ts |
| server/src/__tests__/content-jobs-routes.test.ts |
| server/src/__tests__/content-jobs-sse.test.ts |
|
|
| added |
patterns |
|
|
| fire-and-forget dispatch via void contentJobRunner.dispatch() |
| SSE with res.flushHeaders() + res.write() + req.on("close") cleanup |
| EventEmitter subscription for job progress (no polling) |
| Supertest + vitest mocks for route integration testing |
|
|
| created |
modified |
| server/src/routes/content-jobs.ts |
| server/src/__tests__/content-jobs-routes.test.ts |
| server/src/__tests__/content-jobs-sse.test.ts |
|
|
|
| SSE streams use EventEmitter subscription not polling — no setTimeout/setInterval |
| req.on("close") cleanup prevents listener leaks when client disconnects mid-job |
| Terminal jobs (done/failed) end SSE stream immediately after initial status event |
| jobType validation rejects empty strings (whitespace-only) as well as missing field |
|
| duration |
completed |
tasks_completed |
tasks_total |
files_created |
files_modified |
| ~3 minutes |
2026-04-04 |
2 |
2 |
3 |
1 |
|