Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 42 additions & 26 deletions development/backend/job-queue.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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;
```
Expand Down Expand Up @@ -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
);
```

Expand All @@ -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

Expand Down Expand Up @@ -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

<Info>
For recurring scheduled tasks, see the [Cron Job Scheduling](/development/backend/cron) documentation which integrates with this job queue system.
</Info>

## Migration Path

If scaling beyond single-server becomes necessary, clear upgrade paths exist:
Expand All @@ -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

Expand Down
100 changes: 67 additions & 33 deletions development/frontend/ui/design-button-loading.mdx
Original file line number Diff line number Diff line change
@@ -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
<script setup lang="ts">
import { ref } from 'vue'
import { Button } from '@/components/ui/button'
import { Spinner } from '@/components/ui/spinner'
import { toast } from 'vue-sonner'

const isSubmitting = ref(false)
Expand All @@ -56,26 +45,71 @@ const handleSubmit = async () => {
</script>

<template>
<Button
:loading="isSubmitting"
loading-text="Saving..."
<Button
:disabled="isSubmitting"
@click="handleSubmit"
>
<Spinner v-if="isSubmitting" class="mr-2" />
Save Changes
</Button>
</template>
```

## 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 `<Spinner v-if="isLoading" class="mr-2" />` 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
<!-- Smaller spinner -->
<Spinner v-if="isLoading" class="mr-2 size-3" />

<!-- Different color -->
<Spinner v-if="isLoading" class="mr-2 text-white" />
```

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
<Button :disabled="isSubmitting" @click="handleSubmit">
<Spinner v-if="isSubmitting" class="mr-2" />
{{ isSubmitting ? 'Saving...' : 'Save Changes' }}
</Button>
```

### Delete Button

```vue
<Button
variant="destructive"
:disabled="isDeleting"
@click="handleDelete"
>
<Spinner v-if="isDeleting" class="mr-2" />
Delete
</Button>
```

### With Validation

```vue
<Button
:disabled="!isFormValid || isSubmitting"
@click="handleSubmit"
>
<Spinner v-if="isSubmitting" class="mr-2" />
Submit
</Button>
```

## Related Documentation

Expand Down
Binary file added development/frontend/ui/design-settings-menu.mdx
Binary file not shown.
3 changes: 2 additions & 1 deletion docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@
"/development/frontend/ui/design-system-structured-data",
"/development/frontend/ui/design-system-table",
"/development/frontend/ui/custom-ui-components",
"/development/frontend/ui/siteHeader-with-breadcrumbs"
"/development/frontend/ui/siteHeader-with-breadcrumbs",
"/development/frontend/ui/design-settings-menu"
]
}
]
Expand Down
Loading