Skip to content

Commit fe8763d

Browse files
committed
chore: wip
1 parent 797f589 commit fe8763d

File tree

13 files changed

+1157
-34
lines changed

13 files changed

+1157
-34
lines changed

README.md

Lines changed: 167 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,39 +8,175 @@
88

99
# bun-queue
1010

11-
This is an opinionated TypeScript Starter kit to help kick-start development of your next Bun package.
11+
A Redis-backed job queue built for Bun, inspired by Laravel's Queue system and BullMQ.
1212

1313
## Features
1414

15-
This Starter Kit comes pre-configured with the following:
15+
- Fast and efficient Redis-backed queue system
16+
- Support for delayed jobs, retries, and prioritization
17+
- Rate limiting capabilities
18+
- Job event tracking
19+
- Reliable job processing with concurrency control
20+
- Typesafe API
1621

17-
- 🛠️ [Powerful Build Process](https://github.com/oven-sh/bun) - via Bun
18-
- 💪🏽 [Fully Typed APIs](https://www.typescriptlang.org/) - via TypeScript
19-
- 📚 [Documentation-ready](https://vitepress.dev/) - via VitePress
20-
-[CLI & Binary](https://www.npmjs.com/package/bunx) - via Bun & CAC
21-
- 🧪 [Built With Testing In Mind](https://bun.sh/docs/cli/test) - pre-configured unit-testing powered by [Bun](https://bun.sh/docs/cli/test)
22-
- 🤖 [Renovate](https://renovatebot.com/) - optimized & automated PR dependency updates
23-
- 🎨 [ESLint](https://eslint.org/) - for code linting _(and formatting)_
24-
- 📦️ [pkg.pr.new](https://pkg.pr.new) - Continuous (Preview) Releases for your libraries
25-
- 🐙 [GitHub Actions](https://github.com/features/actions) - runs your CI _(fixes code style issues, tags releases & creates its changelogs, runs the test suite, etc.)_
22+
## Installation
2623

27-
## Get Started
24+
```bash
25+
bun add bun-queue
26+
```
2827

29-
It's rather simple to get your package development started:
28+
## Basic Usage
3029

31-
```bash
32-
# you may use this GitHub template or the following command:
33-
bunx degit stacksjs/bun-starter my-pkg
34-
cd my-pkg
30+
```typescript
31+
import { Queue } from 'bun-queue'
32+
33+
// Create a queue
34+
const emailQueue = new Queue('emails')
35+
36+
// Add a job to the queue
37+
const job = await emailQueue.add({
38+
to: 'user@example.com',
39+
subject: 'Welcome',
40+
body: 'Welcome to our platform!'
41+
})
42+
43+
console.log(`Job ${job.id} added to the queue`)
44+
45+
// Process jobs
46+
emailQueue.process(5, async (job) => {
47+
const { to, subject, body } = job.data
48+
49+
// Update progress
50+
await job.updateProgress(10)
51+
52+
// Simulate sending email
53+
console.log(`Sending email to ${to} with subject: ${subject}`)
54+
await new Promise(resolve => setTimeout(resolve, 1000))
3555

36-
bun i # install all deps
37-
bun run build # builds the library for production-ready use
56+
await job.updateProgress(100)
3857

39-
# after you have successfully committed, you may create a "release"
40-
bun run release # automates git commits, versioning, and changelog generations
58+
return { sent: true, timestamp: Date.now() }
59+
})
4160
```
4261

43-
_Check out the package.json scripts for more commands._
62+
## Job Options
63+
64+
```typescript
65+
// Add a job with options
66+
await queue.add(
67+
{ task: 'process-pdf', url: 'https://example.com/document.pdf' },
68+
{
69+
delay: 5000, // delay for 5 seconds
70+
attempts: 3, // retry up to 3 times
71+
backoff: {
72+
type: 'exponential', // 'fixed' or 'exponential'
73+
delay: 1000, // milliseconds
74+
},
75+
priority: 10, // higher number = higher priority
76+
removeOnComplete: true, // remove job when completed
77+
lifo: false, // process in FIFO order (default)
78+
jobId: 'custom-id', // provide custom job ID
79+
}
80+
)
81+
```
82+
83+
## Delayed Jobs
84+
85+
```typescript
86+
// Add a job that will be processed after 30 seconds
87+
await queue.add(
88+
{ task: 'send-reminder' },
89+
{ delay: 30000 }
90+
)
91+
```
92+
93+
## Job Management
94+
95+
```typescript
96+
// Get a job by ID
97+
const job = await queue.getJob('job-id')
98+
99+
// Get jobs by status
100+
const waitingJobs = await queue.getJobs('waiting')
101+
const activeJobs = await queue.getJobs('active')
102+
const completedJobs = await queue.getJobs('completed')
103+
const failedJobs = await queue.getJobs('failed')
104+
105+
// Get job counts
106+
const counts = await queue.getJobCounts()
107+
console.log(counts) // { waiting: 5, active: 2, completed: 10, failed: 1, delayed: 3, paused: 0 }
108+
109+
// Pause a queue
110+
await queue.pause()
111+
112+
// Resume a queue
113+
await queue.resume()
114+
115+
// Retry a failed job
116+
const failedJob = await queue.getJob('failed-job-id')
117+
await failedJob.retry()
118+
119+
// Remove a job
120+
await job.remove()
121+
122+
// Clear all jobs
123+
await queue.empty()
124+
```
125+
126+
## Rate Limiting
127+
128+
```typescript
129+
import { Queue, RateLimiter } from 'bun-queue'
130+
131+
const queue = new Queue('api-calls')
132+
133+
// Create a rate limiter (100 jobs per minute)
134+
const limiter = new RateLimiter(queue, {
135+
max: 100,
136+
duration: 60000
137+
})
138+
139+
// Check if rate limited before adding job
140+
const { limited, remaining, resetIn } = await limiter.check()
141+
142+
if (!limited) {
143+
await queue.add({ url: 'https://api.example.com/endpoint' })
144+
console.log(`Job added. ${remaining} requests remaining.`)
145+
}
146+
else {
147+
console.log(`Rate limited. Try again in ${resetIn}ms.`)
148+
}
149+
```
150+
151+
## Configuration
152+
153+
```typescript
154+
import { Queue } from 'bun-queue'
155+
156+
// Configure queue with options
157+
const queue = new Queue('tasks', {
158+
redis: {
159+
url: 'redis://username:password@localhost:6379'
160+
// Or provide your own client
161+
// client: myRedisClient
162+
},
163+
prefix: 'myapp', // prefix for Redis keys (default: 'queue')
164+
defaultJobOptions: {
165+
attempts: 5,
166+
backoff: {
167+
type: 'exponential',
168+
delay: 5000
169+
}
170+
}
171+
})
172+
```
173+
174+
## Environment Variables
175+
176+
The library reads these environment variables (in order of precedence):
177+
178+
- `REDIS_URL`: Redis connection string
179+
- Default is `redis://localhost:6379` if not set
44180

45181
## Testing
46182

@@ -50,7 +186,7 @@ bun test
50186

51187
## Changelog
52188

53-
Please see our [releases](https://github.com/stackjs/bun-bull/releases) page for more information on what has changed recently.
189+
Please see our [releases](https://github.com/stackjs/bun-queue/releases) page for more information on what has changed recently.
54190

55191
## Contributing
56192

@@ -60,15 +196,15 @@ Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.
60196

61197
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
62198

63-
[Discussions on GitHub](https://github.com/stacksjs/bun-starter/discussions)
199+
[Discussions on GitHub](<https://github.com/stacksjs/bun-> queue/discussions)
64200

65201
For casual chit-chat with others using this package:
66202

67203
[Join the Stacks Discord Server](https://discord.gg/stacksjs)
68204

69205
## Postcardware
70206

71-
Software that is free, but hopes for a postcard. We love receiving postcards from around the world showing where Stacks is being used! We showcase them on our website too.
207+
"Software that is free, but hopes for a postcard." We love receiving postcards from around the world showing where Stacks is being used! We showcase them on our website too.
72208

73209
Our address: Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, United States 🌎
74210

@@ -86,10 +222,10 @@ The MIT License (MIT). Please see [LICENSE](LICENSE.md) for more information.
86222
Made with 💙
87223

88224
<!-- Badges -->
89-
[npm-version-src]: https://img.shields.io/npm/v/bun-bull?style=flat-square
90-
[npm-version-href]: https://npmjs.com/package/bun-bull
91-
[github-actions-src]: https://img.shields.io/github/actions/workflow/status/stacksjs/bun-starter/ci.yml?style=flat-square&branch=main
92-
[github-actions-href]: https://github.com/stacksjs/bun-starter/actions?query=workflow%3Aci
225+
[npm-version-src]: https://img.shields.io/npm/v/bun-queue?style=flat-square
226+
[npm-version-href]: https://npmjs.com/package/bun-queue
227+
[github-actions-src]: https://img.shields.io/github/actions/workflow/status/stacksjs/bun-queue/ci.yml?style=flat-square&branch=main
228+
[github-actions-href]: https://github.com/stacksjs/bun-queue/actions?query=workflow%3Aci
93229

94-
<!-- [codecov-src]: https://img.shields.io/codecov/c/gh/stacksjs/bun-starter/main?style=flat-square
95-
[codecov-href]: https://codecov.io/gh/stacksjs/bun-starter -->
230+
<!-- [codecov-src]: https://img.shields.io/codecov/c/gh/stacksjs/bun-queue/main?style=flat-square
231+
[codecov-href]: https://codecov.io/gh/stacksjs/bun-queue -->

examples/basic.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { Queue } from '../src'
2+
3+
// Define job data type
4+
interface EmailJob {
5+
to: string
6+
subject: string
7+
body: string
8+
}
9+
10+
async function main() {
11+
// Create email queue
12+
const emailQueue = new Queue<EmailJob>('emails')
13+
14+
// Add 5 jobs to the queue
15+
for (let i = 1; i <= 5; i++) {
16+
const job = await emailQueue.add({
17+
to: `user${i}@example.com`,
18+
subject: `Email ${i}`,
19+
body: `This is test email #${i}`,
20+
}, {
21+
delay: i * 1000, // Each job delayed by 1 second more than the previous
22+
attempts: 3,
23+
})
24+
25+
console.log(`Added job ${job.id} to the queue`)
26+
}
27+
28+
// Process jobs with concurrency of 2
29+
emailQueue.process(2, async (job) => {
30+
const { to, subject, body } = job.data
31+
32+
console.log(`Processing email to ${to}`)
33+
34+
// Update progress
35+
await job.updateProgress(25)
36+
console.log(`[${job.id}] Started processing...`)
37+
38+
// Simulate work
39+
await new Promise(resolve => setTimeout(resolve, 2000))
40+
41+
await job.updateProgress(50)
42+
console.log(`[${job.id}] Sending email...`)
43+
44+
// Simulate more work
45+
await new Promise(resolve => setTimeout(resolve, 1000))
46+
47+
// Randomly fail some jobs to demonstrate retry
48+
if (Math.random() < 0.3) {
49+
throw new Error('Failed to send email')
50+
}
51+
52+
await job.updateProgress(100)
53+
console.log(`[${job.id}] Email sent successfully to ${to}`)
54+
55+
return { sent: true, timestamp: Date.now() }
56+
})
57+
58+
// Display job counts every second
59+
const interval = setInterval(async () => {
60+
const counts = await emailQueue.getJobCounts()
61+
console.log(counts)
62+
63+
// Stop after 30 seconds
64+
if (counts.completed + counts.failed === 5) {
65+
clearInterval(interval)
66+
await emailQueue.close()
67+
console.log('All jobs processed, exiting...')
68+
}
69+
}, 1000)
70+
}
71+
72+
main().catch(console.error)

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
2-
"name": "bun-bull",
2+
"name": "bun-queue",
33
"type": "module",
44
"version": "0.0.0",
5-
"description": "A simple TypeScript starter kit using Bun.",
5+
"description": "A Redis-backed job queue built for Bun.",
66
"author": "Chris Breuer <chris@stacksjs.org>",
77
"license": "MIT",
88
"homepage": "https://github.com/stacksjs/bun-starter#readme",
@@ -55,7 +55,8 @@
5555
"dev:docs": "bun --bun vitepress dev docs",
5656
"build:docs": "bun --bun vitepress build docs",
5757
"preview:docs": "bun --bun vitepress preview docs",
58-
"typecheck": "bun --bun tsc --noEmit"
58+
"typecheck": "bun --bun tsc --noEmit",
59+
"example:basic": "bun examples/basic.ts"
5960
},
6061
"dependencies": {
6162
"@types/semver": "^7.7.0",
@@ -69,6 +70,7 @@
6970
"@stacksjs/docs": "^0.70.23",
7071
"@stacksjs/eslint-config": "^4.10.2-beta.3",
7172
"@types/bun": "^1.2.10",
73+
"@types/node": "^20.11.20",
7274
"bumpp": "^10.1.0",
7375
"bun-plugin-dtsx": "^0.21.9",
7476
"bunfig": "^0.8.2",

src/commands/processJobs-1.lua

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--[[
2+
Process jobs in one atomic operation:
3+
- Get jobs from waiting list
4+
- Move them to active list
5+
- Return job IDs
6+
7+
Input:
8+
KEYS[1] 'waiting' list key
9+
KEYS[2] 'active' list key
10+
KEYS[3] 'paused' key
11+
12+
ARGV[1] count - number of jobs to get
13+
14+
Output:
15+
Array of job IDs, or empty array if no jobs
16+
]]
17+
18+
local waitingKey = KEYS[1]
19+
local activeKey = KEYS[2]
20+
local pausedKey = KEYS[3]
21+
local count = tonumber(ARGV[1])
22+
local rcall = redis.call
23+
24+
-- Check if queue is paused
25+
if rcall("EXISTS", pausedKey) == 1 then
26+
return {}
27+
end
28+
29+
-- Get job IDs from waiting list
30+
local jobIds = rcall("LRANGE", waitingKey, 0, count - 1)
31+
32+
if #jobIds > 0 then
33+
-- Move jobs to active list in bulk
34+
for i = 1, #jobIds do
35+
-- Remove from waiting and add to active
36+
rcall("LREM", waitingKey, 1, jobIds[i])
37+
rcall("LPUSH", activeKey, jobIds[i])
38+
end
39+
end
40+
41+
return jobIds

0 commit comments

Comments
 (0)