Skip to content

Commit 17ef9ed

Browse files
authored
Merge pull request #297 from deploystackio/main
prod
2 parents 0d5f24e + 28dc9f2 commit 17ef9ed

File tree

4 files changed

+111
-60
lines changed

4 files changed

+111
-60
lines changed

development/backend/job-queue.mdx

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Background worker loop that polls the database every second, processes jobs sequ
3030
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.
3131

3232
### Database Tables
33-
Two tables provide persistence: `queueJobs` stores individual jobs with payload and status, while `queueJobBatches` tracks groups of related jobs for progress monitoring.
33+
Two tables provide persistence: `queueJobs` stores individual jobs with payload, status, and result data, while `queueJobBatches` tracks groups of related jobs for progress monitoring.
3434

3535
## Core Concepts
3636

@@ -92,10 +92,12 @@ interface Worker {
9292
interface WorkerResult {
9393
success: boolean;
9494
message?: string;
95-
data?: any;
95+
data?: any; // Persisted to database on job completion
9696
}
9797
```
9898

99+
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.
100+
99101
### Basic Worker Pattern
100102

101103
```typescript
@@ -136,7 +138,8 @@ export class EmailWorker implements Worker {
136138

137139
return {
138140
success: true,
139-
message: 'Email sent successfully'
141+
message: 'Email sent successfully',
142+
data: { sentAt: new Date().toISOString(), recipient: emailPayload.to }
140143
};
141144
} catch (error) {
142145
this.logger.error({
@@ -365,25 +368,25 @@ Jobs transition through these states:
365368

366369
### Database Queries
367370

368-
Check job status:
371+
Check job status and result:
369372

370373
```sql
371-
SELECT id, type, status, created_at, started_at, completed_at, attempts
372-
FROM queue_jobs
374+
SELECT id, type, status, payload, result, error, created_at, completed_at, attempts
375+
FROM "queueJobs"
373376
WHERE id = ?;
374377
```
375378

376379
Monitor batch progress:
377380

378381
```sql
379-
SELECT
382+
SELECT
380383
b.id,
381384
b.total_jobs,
382385
COUNT(CASE WHEN j.status = 'completed' THEN 1 END) as completed,
383386
COUNT(CASE WHEN j.status = 'failed' THEN 1 END) as failed,
384387
COUNT(CASE WHEN j.status = 'processing' THEN 1 END) as processing
385-
FROM queue_job_batches b
386-
LEFT JOIN queue_jobs j ON j.batch_id = b.id
388+
FROM "queueJobBatches" b
389+
LEFT JOIN "queueJobs" j ON j.batch_id = b.id
387390
WHERE b.id = ?
388391
GROUP BY b.id;
389392
```
@@ -414,32 +417,42 @@ For the complete database schema, see [schema.ts](https://github.com/deploystack
414417
### Jobs Table
415418

416419
```sql
417-
CREATE TABLE queue_jobs (
420+
CREATE TABLE "queueJobs" (
418421
id TEXT PRIMARY KEY,
419422
type TEXT NOT NULL,
420-
payload TEXT,
423+
payload TEXT NOT NULL,
421424
status TEXT DEFAULT 'pending',
425+
scheduled_for TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
422426
attempts INTEGER DEFAULT 0,
423427
max_attempts INTEGER DEFAULT 3,
424-
scheduled_for INTEGER,
425-
created_at INTEGER DEFAULT (unixepoch()),
426-
started_at INTEGER,
427-
completed_at INTEGER,
428-
last_error TEXT,
429-
batch_id TEXT,
430-
FOREIGN KEY (batch_id) REFERENCES queue_job_batches(id)
428+
error TEXT,
429+
result TEXT,
430+
batch_id TEXT REFERENCES "queueJobBatches"(id) ON DELETE CASCADE,
431+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
432+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
433+
completed_at TIMESTAMP WITH TIME ZONE
431434
);
432435
```
433436

437+
| Column | Description |
438+
|--------|-------------|
439+
| `payload` | JSON input data passed to the worker |
440+
| `error` | Error message if job failed |
441+
| `result` | JSON output data returned by the worker on success |
442+
434443
### Batches Table
435444

436445
```sql
437-
CREATE TABLE queue_job_batches (
446+
CREATE TABLE "queueJobBatches" (
438447
id TEXT PRIMARY KEY,
439-
name TEXT NOT NULL,
448+
type TEXT NOT NULL,
440449
total_jobs INTEGER NOT NULL,
441-
created_at INTEGER DEFAULT (unixepoch()),
442-
metadata TEXT
450+
completed_jobs INTEGER DEFAULT 0,
451+
failed_jobs INTEGER DEFAULT 0,
452+
status TEXT DEFAULT 'pending',
453+
metadata TEXT,
454+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
455+
completed_at TIMESTAMP WITH TIME ZONE
443456
);
444457
```
445458

@@ -451,8 +464,8 @@ CREATE TABLE queue_job_batches (
451464
2. Check if job's `scheduled_for` time has passed
452465
3. Lock job by setting status to `processing`
453466
4. Execute worker for job type
454-
5. Update status based on result
455-
6. Implement exponential backoff for retries (1s, 2s, 4s, etc.)
467+
5. On success: store worker's `data` in `result` column, set status to `completed`
468+
6. On failure: implement exponential backoff for retries (1s, 2s, 4s, etc.)
456469

457470
### Resource Usage
458471

@@ -486,9 +499,12 @@ Balance between responsiveness and database load. Adequate latency for backgroun
486499

487500
- Not suitable for sub-second latency requirements
488501
- Single-server deployment (no distributed workers)
489-
- No built-in job scheduling (cron-like patterns)
490502
- Sequential processing limits throughput
491503

504+
<Info>
505+
For recurring scheduled tasks, see the [Cron Job Scheduling](/development/backend/cron) documentation which integrates with this job queue system.
506+
</Info>
507+
492508
## Migration Path
493509

494510
If scaling beyond single-server becomes necessary, clear upgrade paths exist:
@@ -501,10 +517,10 @@ Worker interface remains compatible, simplifying migration.
501517

502518
## Related Documentation
503519

520+
- [Cron Job Scheduling](/development/backend/cron) - Schedule recurring tasks using cron expressions
504521
- [Database Management](/development/backend/database/) - Database configuration and schema
505522
- [Global Event Bus](/development/backend/events) - Event system for real-time notifications
506523
- [Logging](/development/backend/logging) - Logging best practices and patterns
507-
- [API Documentation](/development/backend/api/) - REST API endpoints and patterns
508524

509525
## Common Use Cases
510526

development/frontend/ui/design-button-loading.mdx

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,32 @@
11
---
22
title: Button with Loading States
3-
description: Guide for using the enhanced Button component with built-in loading states.
3+
description: Guide for implementing loading states in buttons using the Spinner component.
44
---
55

66

7-
The Button component includes built-in loading state functionality for async operations.
7+
Use the shadcn-vue Spinner component inside buttons to show loading states during async operations.
88

9-
## Component Location
9+
## Component Locations
1010

1111
```
1212
services/frontend/src/components/ui/button/
13-
├── Button.vue # Enhanced button component with loading states
13+
├── Button.vue # Standard shadcn-vue button
1414
└── index.ts # Button variants and exports
15-
```
16-
17-
## Features
18-
19-
- **Automatic spinner** with `Loader2` icon from lucide-vue-next
20-
- **Auto-disable** during loading to prevent double submissions
21-
- **Optional loading text** to display custom messages
22-
- **Size-aware spinner** that scales with button size
23-
- **Works with all variants** (default, destructive, outline, etc.)
2415
25-
## Props
16+
services/frontend/src/components/ui/spinner/
17+
├── Spinner.vue # Animated loading spinner
18+
└── index.ts # Spinner exports
19+
```
2620

27-
| Prop | Type | Default | Description |
28-
|------|------|---------|-------------|
29-
| `loading` | `boolean` | `false` | Shows spinner and disables button |
30-
| `loadingText` | `string` | `undefined` | Optional text during loading |
31-
| `disabled` | `boolean` | `false` | Disable independent of loading |
32-
| `variant` | `string` | `'default'` | Button style variant |
33-
| `size` | `string` | `'default'` | Button size (sm, default, lg, icon) |
21+
## Usage
3422

35-
## Usage Example
23+
Import both Button and Spinner, then conditionally render the Spinner inside the button:
3624

3725
```vue
3826
<script setup lang="ts">
3927
import { ref } from 'vue'
4028
import { Button } from '@/components/ui/button'
29+
import { Spinner } from '@/components/ui/spinner'
4130
import { toast } from 'vue-sonner'
4231
4332
const isSubmitting = ref(false)
@@ -56,26 +45,71 @@ const handleSubmit = async () => {
5645
</script>
5746
5847
<template>
59-
<Button
60-
:loading="isSubmitting"
61-
loading-text="Saving..."
48+
<Button
49+
:disabled="isSubmitting"
6250
@click="handleSubmit"
6351
>
52+
<Spinner v-if="isSubmitting" class="mr-2" />
6453
Save Changes
6554
</Button>
6655
</template>
6756
```
6857

69-
## Implementation Details
58+
## Pattern
59+
60+
The loading button pattern consists of three parts:
61+
62+
1. **Disable the button** - Add `:disabled="isLoading"` to prevent double clicks
63+
2. **Show the spinner** - Add `<Spinner v-if="isLoading" class="mr-2" />` before the text
64+
3. **Keep the text visible** - The button text remains visible during loading
65+
66+
## Spinner Customization
67+
68+
The Spinner component accepts a `class` prop for styling:
69+
70+
```vue
71+
<!-- Smaller spinner -->
72+
<Spinner v-if="isLoading" class="mr-2 size-3" />
73+
74+
<!-- Different color -->
75+
<Spinner v-if="isLoading" class="mr-2 text-white" />
76+
```
7077

71-
The component automatically:
72-
- Displays a spinning `Loader2` icon when `loading` is true
73-
- Hides the original slot content during loading
74-
- Shows `loadingText` alongside the spinner (if provided)
75-
- Disables the button to prevent multiple clicks
76-
- Adjusts spinner size based on button size prop
78+
## Examples
7779

78-
For implementation details, see the source code at `services/frontend/src/components/ui/button/Button.vue`.
80+
### Form Submit Button
81+
82+
```vue
83+
<Button :disabled="isSubmitting" @click="handleSubmit">
84+
<Spinner v-if="isSubmitting" class="mr-2" />
85+
{{ isSubmitting ? 'Saving...' : 'Save Changes' }}
86+
</Button>
87+
```
88+
89+
### Delete Button
90+
91+
```vue
92+
<Button
93+
variant="destructive"
94+
:disabled="isDeleting"
95+
@click="handleDelete"
96+
>
97+
<Spinner v-if="isDeleting" class="mr-2" />
98+
Delete
99+
</Button>
100+
```
101+
102+
### With Validation
103+
104+
```vue
105+
<Button
106+
:disabled="!isFormValid || isSubmitting"
107+
@click="handleSubmit"
108+
>
109+
<Spinner v-if="isSubmitting" class="mr-2" />
110+
Submit
111+
</Button>
112+
```
79113

80114
## Related Documentation
81115

5.2 KB
Binary file not shown.

docs.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@
100100
"/development/frontend/ui/design-system-structured-data",
101101
"/development/frontend/ui/design-system-table",
102102
"/development/frontend/ui/custom-ui-components",
103-
"/development/frontend/ui/siteHeader-with-breadcrumbs"
103+
"/development/frontend/ui/siteHeader-with-breadcrumbs",
104+
"/development/frontend/ui/design-settings-menu"
104105
]
105106
}
106107
]

0 commit comments

Comments
 (0)