https://github.com/noxify/vorsteh-queue
A powerful, ORM-agnostic queue engine for PostgreSQL 12+. Handle background jobs, scheduled tasks, and recurring processes with ease.
https://github.com/noxify/vorsteh-queue
adapters agnostic background-jobs cron delayed-jobs drizzle job-queue mariadb orm postgres postgressql prisma queue queueing scheduler task-queue timezone turbo typescript worker
Last synced: 6 months ago
JSON representation
A powerful, ORM-agnostic queue engine for PostgreSQL 12+. Handle background jobs, scheduled tasks, and recurring processes with ease.
- Host: GitHub
- URL: https://github.com/noxify/vorsteh-queue
- Owner: noxify
- License: mit
- Created: 2025-07-16T13:25:11.000Z (6 months ago)
- Default Branch: main
- Last Pushed: 2025-07-28T17:41:58.000Z (6 months ago)
- Last Synced: 2025-07-28T19:14:07.694Z (6 months ago)
- Topics: adapters, agnostic, background-jobs, cron, delayed-jobs, drizzle, job-queue, mariadb, orm, postgres, postgressql, prisma, queue, queueing, scheduler, task-queue, timezone, turbo, typescript, worker
- Language: TypeScript
- Homepage: https://vorsteh-queue.dev
- Size: 4.03 MB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
Vorsteh Queue
A powerful, ORM-agnostic queue engine for PostgreSQL 12+. Handle background jobs, scheduled tasks, and recurring processes with ease.
## Features
- **Type-safe**: Full TypeScript support with generic job payloads
- **Multiple adapters**: Drizzle ORM (PostgreSQL), Prisma ORM (PostgreSQL), and in-memory implementations
- **Priority queues**: Numeric priority system (lower = higher priority)
- **Delayed jobs**: Schedule jobs for future execution
- **Recurring jobs**: Cron expressions and interval-based repetition
- **UTC-first timezone support**: Reliable timezone handling with UTC storage
- **Progress tracking**: Real-time job progress updates
- **Event system**: Listen to job lifecycle events
- **Graceful shutdown**: Clean job processing termination
## Repository Structure
```
.
├── packages/
│ ├── core/ # Core queue logic and interfaces
│ ├── adapter-drizzle/ # Drizzle ORM adapter (PostgreSQL)
│ └── adapter-prisma/ # Prisma ORM adapter (PostgreSQL)
├── examples/ # Standalone usage examples
└── tooling/ # Shared development tools
```
## Quick Start
### Requirements
- **Node.js 20+**
- **ESM only** - This package is ESM-only and cannot be imported with `require()`
### Installation
```bash
npm install @vorsteh-queue/core @vorsteh-queue/adapter-drizzle
# or
pnpm add @vorsteh-queue/core @vorsteh-queue/adapter-drizzle
```
> **Note**: Make sure your project has `"type": "module"` in package.json or use `.mjs` file extensions.
### Basic Usage
```typescript
// Drizzle ORM with PostgreSQL
import { drizzle } from "drizzle-orm/node-postgres"
import { Pool } from "pg"
import { PostgresQueueAdapter } from "@vorsteh-queue/adapter-drizzle"
import { Queue } from "@vorsteh-queue/core"
interface EmailPayload {
to: string
subject: string
body: string
}
interface EmailResult {
messageId: string
sent: boolean
}
const pool = new Pool({ connectionString: "postgresql://..." })
const db = drizzle(pool)
const queue = new Queue(new PostgresQueueAdapter(db), { name: "my-queue" })
// Prisma ORM with PostgreSQL
import { PrismaClient } from "@prisma/client"
import { PostgresPrismaQueueAdapter } from "@vorsteh-queue/adapter-prisma"
const prisma = new PrismaClient()
const queue = new Queue(new PostgresPrismaQueueAdapter(prisma), { name: "my-queue" })
// Register job handlers
queue.register("send-email", async (job) => {
console.log(`Sending email to ${job.payload.to}`)
// Send email logic here
await sendEmail(job.payload)
// Return result - will be stored in job.result field
return {
messageId: "msg_123",
sent: true,
}
})
// Add jobs
await queue.add("send-email", {
to: "user@example.com",
subject: "Welcome!",
body: "Welcome to our service!",
})
await queue.add(
"send-email",
{ to: "admin@example.com", subject: "Report" },
{
priority: 1, // Higher priority
delay: 5000, // Delay 5 seconds
},
)
// Start processing
queue.start()
```
## Examples
Check out the [examples directory](./examples/) for complete, runnable examples.
> **Note**: All examples demonstrate the UTC-first timezone approach and automatic job cleanup features.
## Priority System
Jobs are processed by priority (lower number = higher priority):
```typescript
await queue.add("urgent-task", payload, { priority: 1 }) // Processed first
await queue.add("normal-task", payload, { priority: 2 }) // Default priority
await queue.add("low-task", payload, { priority: 3 }) // Processed last
```
## Recurring Jobs
```typescript
// Cron expression
await queue.add("daily-report", payload, {
cron: "0 9 * * *", // Every day at 9 AM
})
// Interval with limit
await queue.add("health-check", payload, {
repeat: { every: 30000, limit: 10 }, // Every 30s, 10 times
})
```
## Job Cleanup
```typescript
// Automatic cleanup configuration
const queue = new Queue(adapter, {
name: "my-queue",
removeOnComplete: true, // Remove completed jobs immediately
removeOnFail: false, // Keep failed jobs for debugging
// Or use numbers to keep N jobs
removeOnComplete: 100, // Keep last 100 completed jobs
removeOnFail: 50, // Keep last 50 failed jobs
})
```
## Timezone Support
Vorsteh Queue uses a **UTC-first approach** for reliable timezone handling:
```typescript
// Schedule job for 9 AM New York time - converted to UTC immediately
await queue.add("daily-report", payload, {
cron: "0 9 * * *",
timezone: "America/New_York", // Timezone used for conversion only
})
// Schedule job for specific time in Tokyo - stored as UTC
await queue.add("notification", payload, {
runAt: new Date("2024-01-15T10:00:00"),
timezone: "Asia/Tokyo", // Interprets runAt in Tokyo time
})
// Complex cron with timezone - result always UTC
await queue.add("business-task", payload, {
cron: "*/15 9-17 * * 1-5",
timezone: "Europe/London", // Business hours in London time
})
```
### How It Works
1. **Timezone conversion happens at job creation** - not at execution
2. **All timestamps stored in database are UTC** - no timezone ambiguity
3. **Recurring jobs recalculate using original timezone** - maintains accuracy
4. **Simple and predictable** - no runtime timezone complexity
5. **Server timezone independent** - works consistently across environments
## Job Results
Job handlers can return results that are automatically stored and made available:
```typescript
interface ProcessResult {
processed: number
errors: string[]
duration: number
}
queue.register<{ items: string[] }, ProcessResult>("process-data", async (job) => {
const startTime = Date.now()
const errors: string[] = []
let processed = 0
for (const item of job.payload.items) {
try {
await processItem(item)
processed++
} catch (error) {
errors.push(`Failed to process ${item}: ${error.message}`)
}
}
// Return result - automatically stored in job.result field
return {
processed,
errors,
duration: Date.now() - startTime,
}
})
// Access results in events
queue.on("job:completed", (job) => {
const result = job.result as ProcessResult
console.log(`Processed ${result.processed} items in ${result.duration}ms`)
if (result.errors.length > 0) {
console.warn(`Errors: ${result.errors.join(", ")}`)
}
})
```
## Progress Tracking
```typescript
// Register a job that reports progress
queue.register("process-data", async (job) => {
const items = job.payload.items
for (let i = 0; i < items.length; i++) {
// Process item
await processItem(items[i])
// Update progress (0-100)
const progress = Math.round(((i + 1) / items.length) * 100)
await job.updateProgress(progress)
}
return { processed: items.length }
})
// Listen to progress updates
queue.on("job:progress", (job) => {
console.log(`Job ${job.name}: ${job.progress}% complete`)
})
```
## Event System
```typescript
// Listen to all job lifecycle events
queue.on("job:added", (job) => {
console.log(`✅ Job ${job.name} added to queue`)
})
queue.on("job:processing", (job) => {
console.log(`⚡ Job ${job.name} started processing`)
})
queue.on("job:completed", (job) => {
console.log(`🎉 Job ${job.name} completed successfully`)
console.log(`📊 Result:`, job.result) // Access job result
})
queue.on("job:failed", (job) => {
console.error(`❌ Job ${job.name} failed: ${job.error}`)
})
queue.on("job:retried", (job) => {
console.log(`🔄 Job ${job.name} retrying (attempt ${job.attempts})`)
})
// Queue-level events
queue.on("queue:paused", () => {
console.log("⏸️ Queue paused")
})
queue.on("queue:resumed", () => {
console.log("▶️ Queue resumed")
})
```
## Architecture
### UTC-First Design
- **Database storage**: All timestamps stored as UTC
- **Timezone conversion**: Happens at job creation time
- **No timezone fields**: Database schema contains no timezone columns
- **Predictable behavior**: Same results across different server timezones
### Adapter Pattern
- **Pluggable storage**: Easy to add new database adapters
- **Consistent interface**: Same API across all adapters
- **Type safety**: Full TypeScript support throughout
## Development
This project was developed with AI assistance, combining human expertise with AI-powered code generation to accelerate development while maintaining high code quality standards.
```bash
# Install dependencies
pnpm install
# Run all tests
pnpm test
# Run specific test suites
pnpm test:core # Core package tests
pnpm test:drizzle-postgres # PostgreSQL adapter tests
pnpm test:drizzle-mariadb # MariaDB adapter tests
# Type check
pnpm typecheck
# Lint
pnpm lint
# Build packages
pnpm build
```
## Contributing
Contributions are welcome! Please read our [contributing guidelines](./CONTRIBUTING.md) and submit pull requests to our repository.
## License
MIT License - see [LICENSE](./LICENSE) file for details.