From c4ff734b09350190f0194b4868a19c56876eb81e Mon Sep 17 00:00:00 2001 From: Lasim Date: Mon, 1 Dec 2025 19:42:34 +0100 Subject: [PATCH] docs: Update job queue documentation to include result data and enhance button loading states guide --- development/backend/job-queue.mdx | 68 +++++++----- .../frontend/ui/design-button-loading.mdx | 100 ++++++++++++------ .../frontend/ui/design-settings-menu.mdx | Bin 0 -> 5320 bytes docs.json | 3 +- 4 files changed, 111 insertions(+), 60 deletions(-) create mode 100644 development/frontend/ui/design-settings-menu.mdx diff --git a/development/backend/job-queue.mdx b/development/backend/job-queue.mdx index 7dfe2e2..596d9ab 100644 --- a/development/backend/job-queue.mdx +++ b/development/backend/job-queue.mdx @@ -30,7 +30,7 @@ Background worker loop that polls the database every second, processes jobs sequ Plugin-style pattern where each worker implements the `Worker` interface. Workers are registered by type (e.g., `send_email`, `process_csv`, `sync_registry`) and execute specific job types. ### Database Tables -Two tables provide persistence: `queueJobs` stores individual jobs with payload and status, while `queueJobBatches` tracks groups of related jobs for progress monitoring. +Two tables provide persistence: `queueJobs` stores individual jobs with payload, status, and result data, while `queueJobBatches` tracks groups of related jobs for progress monitoring. ## Core Concepts @@ -92,10 +92,12 @@ interface Worker { interface WorkerResult { success: boolean; message?: string; - data?: any; + data?: any; // Persisted to database on job completion } ``` +The `data` field in `WorkerResult` is automatically stored in the database when a job completes successfully. This allows you to inspect job results through the admin UI or database queries. + ### Basic Worker Pattern ```typescript @@ -136,7 +138,8 @@ export class EmailWorker implements Worker { return { success: true, - message: 'Email sent successfully' + message: 'Email sent successfully', + data: { sentAt: new Date().toISOString(), recipient: emailPayload.to } }; } catch (error) { this.logger.error({ @@ -365,25 +368,25 @@ Jobs transition through these states: ### Database Queries -Check job status: +Check job status and result: ```sql -SELECT id, type, status, created_at, started_at, completed_at, attempts -FROM queue_jobs +SELECT id, type, status, payload, result, error, created_at, completed_at, attempts +FROM "queueJobs" WHERE id = ?; ``` Monitor batch progress: ```sql -SELECT +SELECT b.id, b.total_jobs, COUNT(CASE WHEN j.status = 'completed' THEN 1 END) as completed, COUNT(CASE WHEN j.status = 'failed' THEN 1 END) as failed, COUNT(CASE WHEN j.status = 'processing' THEN 1 END) as processing -FROM queue_job_batches b -LEFT JOIN queue_jobs j ON j.batch_id = b.id +FROM "queueJobBatches" b +LEFT JOIN "queueJobs" j ON j.batch_id = b.id WHERE b.id = ? GROUP BY b.id; ``` @@ -414,32 +417,42 @@ For the complete database schema, see [schema.ts](https://github.com/deploystack ### Jobs Table ```sql -CREATE TABLE queue_jobs ( +CREATE TABLE "queueJobs" ( id TEXT PRIMARY KEY, type TEXT NOT NULL, - payload TEXT, + payload TEXT NOT NULL, status TEXT DEFAULT 'pending', + scheduled_for TIMESTAMP WITH TIME ZONE DEFAULT NOW(), attempts INTEGER DEFAULT 0, max_attempts INTEGER DEFAULT 3, - scheduled_for INTEGER, - created_at INTEGER DEFAULT (unixepoch()), - started_at INTEGER, - completed_at INTEGER, - last_error TEXT, - batch_id TEXT, - FOREIGN KEY (batch_id) REFERENCES queue_job_batches(id) + error TEXT, + result TEXT, + batch_id TEXT REFERENCES "queueJobBatches"(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + completed_at TIMESTAMP WITH TIME ZONE ); ``` +| Column | Description | +|--------|-------------| +| `payload` | JSON input data passed to the worker | +| `error` | Error message if job failed | +| `result` | JSON output data returned by the worker on success | + ### Batches Table ```sql -CREATE TABLE queue_job_batches ( +CREATE TABLE "queueJobBatches" ( id TEXT PRIMARY KEY, - name TEXT NOT NULL, + type TEXT NOT NULL, total_jobs INTEGER NOT NULL, - created_at INTEGER DEFAULT (unixepoch()), - metadata TEXT + completed_jobs INTEGER DEFAULT 0, + failed_jobs INTEGER DEFAULT 0, + status TEXT DEFAULT 'pending', + metadata TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + completed_at TIMESTAMP WITH TIME ZONE ); ``` @@ -451,8 +464,8 @@ CREATE TABLE queue_job_batches ( 2. Check if job's `scheduled_for` time has passed 3. Lock job by setting status to `processing` 4. Execute worker for job type -5. Update status based on result -6. Implement exponential backoff for retries (1s, 2s, 4s, etc.) +5. On success: store worker's `data` in `result` column, set status to `completed` +6. On failure: implement exponential backoff for retries (1s, 2s, 4s, etc.) ### Resource Usage @@ -486,9 +499,12 @@ Balance between responsiveness and database load. Adequate latency for backgroun - Not suitable for sub-second latency requirements - Single-server deployment (no distributed workers) -- No built-in job scheduling (cron-like patterns) - Sequential processing limits throughput + +For recurring scheduled tasks, see the [Cron Job Scheduling](/development/backend/cron) documentation which integrates with this job queue system. + + ## Migration Path If scaling beyond single-server becomes necessary, clear upgrade paths exist: @@ -501,10 +517,10 @@ Worker interface remains compatible, simplifying migration. ## Related Documentation +- [Cron Job Scheduling](/development/backend/cron) - Schedule recurring tasks using cron expressions - [Database Management](/development/backend/database/) - Database configuration and schema - [Global Event Bus](/development/backend/events) - Event system for real-time notifications - [Logging](/development/backend/logging) - Logging best practices and patterns -- [API Documentation](/development/backend/api/) - REST API endpoints and patterns ## Common Use Cases diff --git a/development/frontend/ui/design-button-loading.mdx b/development/frontend/ui/design-button-loading.mdx index 111de18..62ebd3d 100644 --- a/development/frontend/ui/design-button-loading.mdx +++ b/development/frontend/ui/design-button-loading.mdx @@ -1,43 +1,32 @@ --- title: Button with Loading States -description: Guide for using the enhanced Button component with built-in loading states. +description: Guide for implementing loading states in buttons using the Spinner component. --- -The Button component includes built-in loading state functionality for async operations. +Use the shadcn-vue Spinner component inside buttons to show loading states during async operations. -## Component Location +## Component Locations ``` services/frontend/src/components/ui/button/ -├── Button.vue # Enhanced button component with loading states +├── Button.vue # Standard shadcn-vue button └── index.ts # Button variants and exports -``` - -## Features - -- **Automatic spinner** with `Loader2` icon from lucide-vue-next -- **Auto-disable** during loading to prevent double submissions -- **Optional loading text** to display custom messages -- **Size-aware spinner** that scales with button size -- **Works with all variants** (default, destructive, outline, etc.) -## Props +services/frontend/src/components/ui/spinner/ +├── Spinner.vue # Animated loading spinner +└── index.ts # Spinner exports +``` -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `loading` | `boolean` | `false` | Shows spinner and disables button | -| `loadingText` | `string` | `undefined` | Optional text during loading | -| `disabled` | `boolean` | `false` | Disable independent of loading | -| `variant` | `string` | `'default'` | Button style variant | -| `size` | `string` | `'default'` | Button size (sm, default, lg, icon) | +## Usage -## Usage Example +Import both Button and Spinner, then conditionally render the Spinner inside the button: ```vue ``` -## Implementation Details +## Pattern + +The loading button pattern consists of three parts: + +1. **Disable the button** - Add `:disabled="isLoading"` to prevent double clicks +2. **Show the spinner** - Add `` before the text +3. **Keep the text visible** - The button text remains visible during loading + +## Spinner Customization + +The Spinner component accepts a `class` prop for styling: + +```vue + + + + + +``` -The component automatically: -- Displays a spinning `Loader2` icon when `loading` is true -- Hides the original slot content during loading -- Shows `loadingText` alongside the spinner (if provided) -- Disables the button to prevent multiple clicks -- Adjusts spinner size based on button size prop +## Examples -For implementation details, see the source code at `services/frontend/src/components/ui/button/Button.vue`. +### Form Submit Button + +```vue + +``` + +### Delete Button + +```vue + +``` + +### With Validation + +```vue + +``` ## Related Documentation diff --git a/development/frontend/ui/design-settings-menu.mdx b/development/frontend/ui/design-settings-menu.mdx new file mode 100644 index 0000000000000000000000000000000000000000..31949e3e59d64e35f3a1b5aedca4b7ad28ba6908 GIT binary patch literal 5320 zcmcgw&u`nv72Y$?J@)V_FJLDyrS2g>Ay{s+o1!(4c7r(S9)bWGkt1nfiqy^w*YdsD6){Y#g1l53Y)v-PGbr}Ub# zYO}4JS`?a`$@M~6D%H-c6fX#~r7k@!D@(2wv{ftZ&M{UL)h$GmUt{}PrKiHyf(+SByFsPEy!qpP}qpeq7Z@QfXvkH^lNc_b= z2J>|GzN!(xx2C*#aU|iobyW~^LjSC+`Gs{YIyWYNfQhEe^_MXMbo_ThfB&+rtaswV zfqNZ$`()ssBpEs+M0{(j9a5IFqIAZ=^`6{TWt@-;?QgX%L+m+#h!mWhT(n?dw_hei z7k^6A!CAqT$oBrqXftJNQ{)zs)ATaAnDiH2wzdqF7lBD4KaQ*}kWx)Dva^xC)k*_E zc1#}tBMBS9wk;V)Z8TxQJ-y?>d-|~7YW%#>OH~6F{1hQg_sKm#hri)ZHz1RkhP=6) zHHU;bU;($uV~waS8AAN_W1CHDt#Wp(T2#OV<9)?Ri1VUr*^@sprh*CLpuT^CXJv9W zdYxrejexZaMiPwnjibYv3g65|5j_)lWoZgMx`ZeAVd#I}a2OLg^D;>!%&Z&|`&s>d zjT#k4YjpV||NLqdm@1AEn^k6h?C_Q@lPj259wVXXgAK2!(EVD-D!C@3vMBjA!ufigwFqN)n5N_k$Y!fAf`X)ReUxx7CUUSn|al%u(o!fg!Tk zM%f)nSKbDx`L@o0ej)Hs7lfPR_K&uZJx^u!$M-yWNVXry(e_QRC=$3elR)W^{@I z)f06(N;vWVbB!vOgCz&N7I4);i29yCLb;@LqpoWjh6r3%iS?X~KB8=XMZ?40jgk%a zzoI1L{Aqt4@Jax5=Pk)uVI;wLB&kQwd=%KRU@*GZU4bk>tT>!@#?`8jyqCtKq0^@- zRTM{)SY_y=L)s!=(UyQ3AgzOeBQzsmPj1Va7)a|8O7gVh>`*v(5xBfimgJzcgHTa5RIHXfWK8+0$kW zL_T56zx&5VbDuV8IMX&1!Q4S)V;V9@zZ&}MJv!FS?~J@&qK=>hJ?6I7|G&|7sB5jz zCmi>#Tk(6{#$FrxQ@}M5baC(2VXmQ7db2Op#$@vG8Yt+E@+wKtPLxeW<-U|ZUK-Xz zMOW6UJylCG5a@mj-@(R6Twtt@tRhSFr;nHrj%oe>f2K5pZ%?~aXiH-Adli;Il~Tv`cZNF&mvVO6<==ZG3?r z+$FS%jc9tK_voJ*Ka5QtAv*wnp!I3|dWDSbC$G7hwZqq=%N=x`rr zo@>8HyS7S_4``#Zc>9Bn$n`I~@HJ#sC5rQnF2O*!IpFMW?jz8FZ6E^-l+w$WuY0#j zFJI!@VzEk{#`0VNVjj&=_sZ$1S15J3jauS%1^ruY>P^#iw9QB8LFOX;stj=ow^kxw zL~hWm<$ZB${&1Q0&z`*cGrCkBl^xyDL2^iL$!!rU`Z!p&s0!S)iQeco`S