diff --git a/raw/jobs-queue__jobs.md b/raw/_processed/jobs-queue__jobs.md similarity index 100% rename from raw/jobs-queue__jobs.md rename to raw/_processed/jobs-queue__jobs.md diff --git a/wiki/_master-index.md b/wiki/_master-index.md index 15108e8..fca0bb7 100644 --- a/wiki/_master-index.md +++ b/wiki/_master-index.md @@ -35,7 +35,7 @@ This 3-hop pattern works for hundreds of articles without vector search. | [[wiki/reports/_index\|reports/]] | Weekly and monthly summaries — generate: `uv run python scripts/report-generator.py --weekly` | 1 | | [[wiki/infrastructure/_index\|infrastructure/]] | Server inventory: all 10 SSH hosts — optical, optical-dev, optical-prod, baic, librechat, modocmms, box-cli, aimpress, pve | 12 | | [[wiki/testing/_index\|testing/]] | Web app testing: functional, performance, security, UI types; TDD/BDD/Agile methodologies; Selenium/Cypress/Playwright/JMeter/OWASP ZAP tools | 1 | -| [[wiki/payloadcms/_index\|payloadcms/]] | Full Payload CMS reference — getting started, config, database (Postgres/MongoDB/SQLite), all 22 field types, access control, hooks, authentication (cookies, JWT, API keys, custom strategies, token data), admin UI, custom components, Lexical rich text, live preview, versions/drafts, Local/REST/GraphQL APIs, queries, plugins, jobs queue, upload, ecommerce, production deploy, TypeScript, migration guides, i18n, localization, hierarchy | 96 | +| [[wiki/payloadcms/_index\|payloadcms/]] | Full Payload CMS reference — getting started, config, database (Postgres/MongoDB/SQLite), all 22 field types, access control, hooks, authentication (cookies, JWT, API keys, custom strategies, token data), admin UI, custom components, Lexical rich text, live preview, versions/drafts, Local/REST/GraphQL APIs, queries, plugins, jobs queue, upload, ecommerce, production deploy, TypeScript, migration guides, i18n, localization, hierarchy | 97 | | [[wiki/shared-patterns/_index\|shared-patterns/]] | Oliver Agency standard library patterns: httpx, structlog, pydantic-settings, alembic — reuse before writing from scratch | 4 | | [[wiki/mistakes/_index\|mistakes/]] | Anti-patterns extracted from sessions — per-stack running lists (fastapi, react, docker, postgres, general) — injected at session start | 5 | diff --git a/wiki/payloadcms/_index.md b/wiki/payloadcms/_index.md index 8fce6af..1f26a9a 100644 --- a/wiki/payloadcms/_index.md +++ b/wiki/payloadcms/_index.md @@ -8,6 +8,7 @@ | [[wiki/payloadcms/admin-preferences\|Admin Preferences]] | Per-user persistent preferences via `usePreferences` hook — built-ins, DB schema, REST/GraphQL, usage pattern | raw/admin__preferences.md | 2026-05-15 | | [[wiki/payloadcms/admin-preview\|Admin Preview & Draft Preview]] | Preview button config, draft preview flow with Next.js (3 steps), conditional preview, PREVIEW_SECRET guard | raw/admin__preview.md | 2026-05-15 | | [[wiki/payloadcms/jobs-queue\|Jobs Queue]] | Full reference: Tasks, Jobs, Queues, Workflows, Schedules, concurrency controls, worker strategies, gotchas | raw/jobs-queue__*.md | 2026-05-15 | +| [[wiki/payloadcms/jobs-queue-jobs\|Jobs Queue — Jobs Detail]] | Queuing from hooks/endpoints/server actions, job options (waitUntil/queue/log), full status schema, access control (jobs.access + overrideAccess), cancellation (cancelByID/cancel/JobCancelledError) | raw/jobs-queue__jobs.md | 2026-05-15 | | [[wiki/payloadcms/admin\|Admin Panel (full)]] | Complete admin config reference: preview, draft-preview, preferences API, all React hooks | raw/admin__*.md | 2026-05-15 | | [[wiki/payloadcms/custom-components\|Custom Components]] | Root slots, dashboard widgets, edit/list view slots, document views, custom views, providers | raw/custom-components__*.md | 2026-05-15 | diff --git a/wiki/payloadcms/jobs-queue-jobs.md b/wiki/payloadcms/jobs-queue-jobs.md new file mode 100644 index 0000000..091e7a9 --- /dev/null +++ b/wiki/payloadcms/jobs-queue-jobs.md @@ -0,0 +1,201 @@ +--- +title: "Jobs Queue — Jobs: Queuing, Status, Access Control, Cancellation" +aliases: [payload-jobs-detail, jobs-queue-detail] +tags: [payloadcms, jobs-queue, typescript] +sources: [raw/jobs-queue__jobs.md] +created: 2026-05-15 +updated: 2026-05-15 +--- + +# Jobs Queue — Jobs (Detail) + +A **Job** is a runtime instance of a Task or Workflow stored in `payload-jobs`. + +See the full overview at [[wiki/payloadcms/jobs-queue|Jobs Queue Overview]]. + +--- + +## Queuing a Job + +```ts +// Queue a Workflow +await payload.jobs.queue({ + workflow: 'createPostAndUpdate', + input: { title: 'my title' }, +}) + +// Queue a single Task +await payload.jobs.queue({ + task: 'createPost', + input: { title: 'my title' }, +}) +``` + +### Where to Queue + +| Location | Pattern | +|----------|---------| +| Collection `afterChange` hook | `req.payload.jobs.queue(...)` — most common | +| Field `afterChange` hook | Trigger on specific field change (e.g. image variants on upload change) | +| Custom REST endpoint | `req.payload.jobs.queue(...)`, return `job.id` to caller | +| Next.js Server Action | `getPayload({ config })` → `payload.jobs.queue(...)` | + +#### Collection hook example + +```ts +afterChange: [async ({ req, doc, operation }) => { + if (operation === 'update' && doc.status === 'published') { + await req.payload.jobs.queue({ + task: 'notifySubscribers', + input: { postId: doc.id }, + }) + } +}] +``` + +#### Server Action example + +```ts +'use server' +import { getPayload } from 'payload' +import config from '@payload-config' + +export async function scheduleEmail(userId: string) { + const payload = await getPayload({ config }) + await payload.jobs.queue({ task: 'sendEmail', input: { userId } }) +} +``` + +--- + +## Job Options + +```ts +await payload.jobs.queue({ + task: 'sendEmail', + input: { userId: '123' }, + waitUntil: new Date('2024-12-25T00:00:00Z'), // schedule for future + queue: 'high-priority', // assign to named queue + log: [{ message: 'Queued by admin', createdAt: new Date().toISOString() }], + req, // pass request context for access control +}) +``` + +| Option | Description | +|--------|-------------| +| `waitUntil` | Run at specific future date/time | +| `queue` | Named queue (default: `'default'`) | +| `log` | Custom log entries for debugging/tracking | +| `req` | Request context (required for access control) | + +--- + +## Job Status + +### Check after queuing + +```ts +const job = await payload.jobs.queue({ task: 'processPayment', input: { orderId: '123' } }) + +const updatedJob = await payload.findByID({ + collection: 'payload-jobs', + id: job.id, +}) + +updatedJob.completedAt // ISO string when finished (null if pending) +updatedJob.hasError // true if failed +updatedJob.taskStatus // per-task details (for workflows) +``` + +### Full job document schema + +```ts +{ + id: 'job_123', + taskSlug: 'sendEmail', // or workflowSlug for workflows + input: { userId: '123' }, + completedAt: '2024-01-15...', // null if still pending + hasError: false, + totalTried: 1, // number of execution attempts + processing: false, // true if currently running + taskStatus: { // per-task (workflows only) + sendEmail: { + '1': { complete: true, output: { emailSent: true } } + } + }, + log: [{ message: 'Job started', createdAt: '...' }], +} +``` + +--- + +## Access Control + +By default, `payload.jobs.*` Local API calls **bypass** access control. + +### Define access rules in Jobs Config + +```ts +jobs: { + access: { + queue: ({ req }) => req.user?.roles?.includes('admin'), + run: ({ req }) => req.user?.roles?.includes('admin'), + cancel: ({ req }) => req.user?.roles?.includes('admin'), + }, +} +``` + +- Each function receives `req` and returns `boolean` +- If undefined, authenticated users can perform the operation +- **Do NOT** set access control on the `payload-jobs` collection directly — may be deprecated + +### Enable access control in Local API + +```ts +const req = await createLocalReq({ user }, payload) + +await payload.jobs.queue({ + workflow: 'createPost', + input: { title: 'My Post' }, + overrideAccess: false, // enforce access control + req, // must include user context +}) +``` + +--- + +## Cancelling Jobs + +```ts +// Cancel single job +await payload.jobs.cancelByID({ id: createdJob.id }) + +// Cancel multiple jobs by query +await payload.jobs.cancel({ + where: { workflowSlug: { equals: 'createPost' } }, +}) +``` + +- Cancelling a **running** job: current task finishes, subsequent tasks are skipped +- Cancellation is checked **between tasks**, not mid-task +- From inside a handler: `throw new JobCancelledError('reason')` + +--- + +## Key Takeaways + +- Jobs are instances of Tasks or Workflows stored in `payload-jobs` +- Queue from anywhere: collection hooks (most common), field hooks, endpoints, server actions +- `waitUntil` for one-time future scheduling; `queue` to segment execution pools +- Full job status is in `payload-jobs` collection — `completedAt`, `hasError`, `taskStatus` +- Access control lives in `jobs.access` config, **not** on the collection; use `overrideAccess: false` + `req` to enforce +- Cancellation is inter-task (current task finishes); use `JobCancelledError` from within handlers + +--- + +## Related + +- [[wiki/payloadcms/jobs-queue|Jobs Queue — Full Reference]] (tasks, workflows, queues, schedules, gotchas) +- [[wiki/payloadcms/hooks-collections|Collection Hooks]] (most common place to queue jobs) +- [[wiki/payloadcms/authentication-overview|Authentication Overview]] (access control patterns) +- [[wiki/payloadcms/local-api|Local API]] (`payload.jobs.*`, `overrideAccess`)