Skip to content

feat: add .id() method for job deduplication#12

Open
jigs1996 wants to merge 3 commits intoboringnode:mainfrom
jigs1996:feat/job-deduplication
Open

feat: add .id() method for job deduplication#12
jigs1996 wants to merge 3 commits intoboringnode:mainfrom
jigs1996:feat/job-deduplication

Conversation

@jigs1996
Copy link
Copy Markdown

Summary

  • Add optional .id() builder method to JobDispatcher for preventing duplicate jobs
  • When a custom ID is provided, the adapter uses atomic insert-if-not-exists to silently skip duplicates
  • Custom IDs are auto-prefixed with the job name to prevent cross-job collisions
  • Empty string validation on .id() throws a clear error

How it works

// First dispatch - job is created
await SendInvoiceJob.dispatch({ orderId: 123 }).id('order-123').run()

// Second dispatch with same ID - silently skipped
await SendInvoiceJob.dispatch({ orderId: 123 }).id('order-123').run()

// Different job types can use the same custom ID without conflict
await SendReceiptJob.dispatch({ orderId: 123 }).id('order-123').run()

Without .id(), behavior is completely unchanged — jobs use auto-generated UUIDs as before.

Adapter implementations

Adapter Dedup mechanism
Redis HSETNX + conditional ZADD via Lua script
Knex INSERT ... ON CONFLICT (id, queue) DO NOTHING
Fake Map existence check before insert
Sync No changes (executes inline)

Breaking changes

None. All changes are additive and opt-in:

  • unique?: boolean is an optional field on JobData
  • .id() is a new method — existing code is unaffected
  • No schema migration required (uses existing (id, queue) primary key)
  • No new required adapter methods

@RomainLanz RomainLanz requested a review from Copilot April 10, 2026 12:52
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional JobDispatcher.id() builder to enable job deduplication by using a custom ID (auto-prefixed with the job name) and a new unique flag that adapters interpret as “insert-if-not-exists”.

Changes:

  • Introduces JobDispatcher.id() and sets JobData.unique when a custom ID is used.
  • Implements adapter-level dedup behavior for Redis (Lua + HSETNX) and Knex (ON CONFLICT DO NOTHING), plus in-memory fakes.
  • Adds test coverage for custom IDs and unique-push behavior, and documents the feature in the README.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/job_dispatcher.spec.ts Adds tests for .id() behavior (validation, prefixing, unique flag, dedup).
tests/fake_adapter.spec.ts Adds tests ensuring FakeAdapter skips duplicate pushes when unique is set.
tests/_utils/register_driver_test_suite.ts Extends shared driver suite to test unique/dedup semantics across adapters.
tests/_mocks/memory_adapter.ts Adds unique existence checks for the in-test memory adapter.
src/types/main.ts Adds unique?: boolean to JobData with explanatory docs.
src/job_dispatcher.ts Adds .id() builder and wires prefixed IDs + unique flag into dispatched job data.
src/drivers/redis_adapter.ts Adds Lua scripts to atomically skip duplicates for unique jobs (immediate and delayed).
src/drivers/knex_adapter.ts Uses onConflict(['id','queue']).ignore() when unique is set.
src/drivers/fake_adapter.ts Adds unique existence checks before enqueueing/scheduling fake jobs.
README.md Documents job deduplication via .id() and its interaction with retention.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Julien-R44
Copy link
Copy Markdown
Member

Just a quick thought,what about this API instead :

await SendInvoiceJob
  .dispatch({ orderId: 123 })
  .dedup({ id: 'order-124' })
  .run()

That would make the API easier to extend later if we want to support more advanced deduplication modes, similar to BullMQ, for example TTL-based deduplication, replace/debounce behavior, or other options. SEe https://docs.bullmq.io/guide/jobs/deduplication

For example later we could have :

await SendInvoiceJob
  .dispatch({ orderId: 123 })
  .dedup({
    id: 'order-123',
    ttl: '5s',
  })
  .run()

@jigs1996
Copy link
Copy Markdown
Author

https://docs.bullmq.io/guide/jobs/deduplication

That’s a good idea. I’ll take this up on Saturday.

@jigs1996 jigs1996 force-pushed the feat/job-deduplication branch from 422a360 to eda02cf Compare April 10, 2026 17:47
@jigs1996 jigs1996 force-pushed the feat/job-deduplication branch from eda02cf to 69c0635 Compare April 10, 2026 17:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants