tj_lavin
Version, currently 0.3.03 versions
- 0.3.0latestMay 7, 2026
- 0.2.0not indexedMay 7, 2026
- 0.1.1not indexedMay 7, 2026
github.com/russ/tj_lavin
Type-Safe Crystal Job Queue with Priority & Delayed Execution
8 stars
0 dependents
License: NOASSERTION
Installation
# Add this to your shard.yml
dependencies:
tj_lavin:
github: russ/tj_lavin
version: ~> 0.3.0Then run:
shards installshard.yml
- Crystal
1.17.0- Author
- Russell Smith <russ@bashme.org>
- Target
tj_lavinfrom src/tj_lavin.cr
Dependencies
Runtime Dependencies
- amqp-client*github: cloudamqp/amqp-client.cr
Development Dependencies
- ameba~> 1.5.0github: crystal-ameba/amebadev
README
[](https://github.com/russ/tj_lavin/actions/workflows/ci.yml)
[](https://github.com/russs/tj_lavin/releases)
[](https://github.com/russ/tj_lavin/blob/master/LICENSE)
# TJ Lavin 🚴
> *"Keep on peddalin'!"* - A Crystal job queue library for background processing
TJ Lavin is a lightweight Crystal wrapper around [LavinMQ](https://lavinmq.com) that makes background job processing simple and fun. Named after the legendary BMX rider and Challenge host, it's designed to keep your application pedaling smoothly even under pressure.
## ✨ Features
- 🔥 **Simple Setup** - Get up and running in minutes
- ⚡ **Priority Queues** - Handle urgent jobs first
- 📋 **Named Queues** - Route jobs to dedicated worker pools
- ⏰ **Delayed Jobs** - Schedule jobs for future execution
- 🛡️ **Error Handling** - Robust failure management
- 🎯 **Type Safety** - Crystal's type system keeps your jobs safe
- 🔧 **Lucky Integration** - Works seamlessly with Lucky/Avram applications
## 🚀 Quick Start
### Installation
Add this to your `shard.yml`:
```yaml
dependencies:
tj_lavin:
github: russ/tj_lavin
```
Then run:
```bash
shards install
```
### Configuration
Configure TJ Lavin in your application:
```crystal
TJLavin.configure do |settings|
settings.amqp_url = ENV["AMQP_URL"]
settings.routing_key = ENV["AMQP_ROUTING_KEY"]? # defaults to "tjlavin"
settings.default_exchange = ENV["AMQP_DEFAULT_EXCHANGE"]? # defaults to ""
settings.delayed_exchange = ENV["AMQP_DELAYED_EXCHANGE"]? # defaults to "tjlavin.delayed"
end
```
### Creating a Job
Define your background jobs by inheriting from `QueuedJob`:
```crystal
class BMXWorker < TJLavin::QueuedJob
param name : String
param skill_level : Int32 = 5
def perform
puts "Keep on peddalin' #{name}! Your skill level is #{skill_level}/10 🚴♂️"
# Your job logic here
end
end
```
### Enqueuing Jobs
Queue up some work:
```crystal
# Simple job
BMXWorker.new(name: "BMX Bandit").enqueue
# High priority job (0-255, higher = more priority, default is 0)
BMXWorker.new(name: "Speed Demon").enqueue(priority: 10)
# Delayed job
BMXWorker.new(name: "Future Rider").enqueue(delay: 30.seconds)
```
### Named Queues
By default, all jobs use the `"tjlavin"` queue. You can assign jobs to specific queues using the `queue` macro:
```crystal
class SendEmailWorker < TJLavin::QueuedJob
# No queue declaration — uses the default "tjlavin" queue
param user_id : UUID
def perform
# ...
end
end
class MigrateGifMediaWorker < TJLavin::QueuedJob
queue "media_processing"
param media_id : UUID
def perform
# ...
end
end
```
Enqueuing works the same way — the job already knows its queue:
```crystal
SendEmailWorker.new(user_id: user.id).enqueue
MigrateGifMediaWorker.new(media_id: media.id).enqueue(priority: 100)
```
### Running Workers
You can control which queues a worker process consumes from:
```crystal
# Consume from ALL registered queues (default)
TJLavin::Runner.start
# Consume only from the default queue (e.g., web server workers)
TJLavin::Runner.start(queues: ["tjlavin"])
# Consume only media processing jobs (e.g., dedicated media workers)
TJLavin::Runner.start(queues: ["media_processing"])
# Consume from multiple specific queues
TJLavin::Runner.start(queues: ["tjlavin", "media_processing"])
```
For Lucky/Avram applications, set up your worker process:
```crystal
# worker.cr
require "./app"
Habitat.raise_if_missing_settings!
if LuckyEnv.development?
Avram::Migrator::Runner.new.ensure_migrated!
Avram::SchemaEnforcer.ensure_correct_column_mappings!
end
# Disable query cache for better performance in workers
Avram.settings.query_cache_enabled = false
# Start processing jobs
TJLavin::Runner.start
```
Then run your worker:
```bash
crystal worker.cr
```
## Testing with specs
To test your workers, include a spec helper like this:
```crystal
module TJLavin
abstract class QueuedJob < Job
ENQUEUED_JOBS = [] of String
def enqueue(priority : Int32 = 0, delay : Time::Span = 0.seconds) : JobRun
ENQUEUED_JOBS << self.class.name
TJLavin::JobRun.new("nothing")
end
end
end
Spec.before_each do
TJLavin::QueuedJob::ENQUEUED_JOBS.clear
end
```
Then check to make sure your job was enqueued:
```crystal
TJLavin::QueuedJob::ENQUEUED_JOBS.should contain("MyWorker")
```
## 📚 Documentation
- **Job Parameters**: Use the `param` macro to define typed parameters for your jobs
- **Error Handling**: Failed jobs are automatically rejected and logged
- **Monitoring**: Built-in logging shows job processing status
- **Serialization**: Automatic parameter serialization/deserialization
## 🤝 Contributing
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request
## 📄 License
Apache License 2.0 - see LICENSE file for details.
---
*Keep on peddalin'!* 🚴♂️
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This release
- Version
0.3.0- Tagged
- May 7, 2026
- Commit
dcd46a54f803- Crystal
1.17.0- Indexed
- yes
Dependents
No indexed shard depends on this one yet.
Repository
github.com/russ/tj_lavin
Metadata
- Created
- Aug 12, 2026
- Updated
- Aug 14, 2026
- Synced
- Aug 13, 2026
- Versions
- 3