PHPackages                             tetthys/safejob - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Queues &amp; Workers](/categories/queues)
4. /
5. tetthys/safejob

ActiveLibrary[Queues &amp; Workers](/categories/queues)

tetthys/safejob
===============

Financial-grade idempotent job framework with Laravel integration

0.0.1(4mo ago)11MITPHPPHP ^8.5

Since Dec 30Pushed 4mo agoCompare

[ Source](https://github.com/tetthys/safe-job)[ Packagist](https://packagist.org/packages/tetthys/safejob)[ RSS](/packages/tetthys-safejob/feed)WikiDiscussions dev Synced 1mo ago

READMEChangelog (1)Dependencies (5)Versions (2)Used By (0)

Tetthys SafeJob
===============

[](#tetthys-safejob)

**Tetthys SafeJob** is a financial-grade job execution framework that guarantees
**exactly-once success semantics** even under retries, duplicate dispatches, or worker crashes.

It is designed for high-risk domains such as payments, wallets, settlements, and external side effects, and integrates naturally with **Laravel 12 Queue** while remaining framework-agnostic at the core.

---

Why SafeJob?
------------

[](#why-safejob)

Laravel jobs are *at-least-once* by default.

This means:

- A job **may run more than once**
- `handle()` may be re-executed after partial success
- Side effects (payments, events, notifications) can be duplicated

SafeJob solves this by **splitting execution into two strictly separated phases**:

1. **Retry-safe work** (can run many times)
2. **Exactly-once success effects** (runs globally only once)

---

Execution Pipeline
------------------

[](#execution-pipeline)

Every SafeJob follows the same fixed pipeline:

```

┌────────────────────┐
│ tryLease()         │  Acquire global execution right
│ (DB row + TTL)     │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ perform()          │  Retry-safe, idempotent work
│ (may run many)     │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ finalizeSuccess()  │  Atomically mark SUCCEEDED
│ (exactly once)     │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ onSucceededOnce()  │  Irreversible side effects
│ (exactly once)     │
└────────────────────┘

```

If an exception occurs:

- If allowed by `FailurePolicy` → finalize as **FAILED**
- Otherwise → exception is rethrown and normal Laravel retry applies

---

Installation
------------

[](#installation)

```
composer require tetthys/safejob
php artisan migrate
```

This will:

- Register the Laravel service provider automatically
- Install the `safe_jobs` table for lease &amp; state tracking

---

Core Concepts
-------------

[](#core-concepts)

### SafeJob (Business Logic)

[](#safejob-business-logic)

You implement **SafeJob**, not Laravel Job logic.

```
use Tetthys\SafeJob\Core\Contracts\SafeJob;
use Tetthys\SafeJob\Core\Contracts\Context;
use Tetthys\SafeJob\Core\Contracts\IdempotencyKey;
use Tetthys\SafeJob\Core\Value\Outcome;

final class ChargeWalletSafeJob implements SafeJob
{
    public function __construct(
        private string $chargeId
    ) {}

    public function key(): IdempotencyKey
    {
        return new IdempotencyKey('charge:' . $this->chargeId);
    }

    public function perform(Context $ctx): Outcome
    {
        // Retry-safe work only:
        // - idempotent DB writes
        // - upserts
        // - unique constraints
        return Outcome::ranSuccess();
    }

    public function onSucceededOnce(Context $ctx): void
    {
        // Exactly-once side effects:
        // - emit domain event
        // - send notification
        // - publish message
    }
}
```

Rules:

- `perform()` **must be safe to run multiple times**
- `onSucceededOnce()` **must assume it runs only once globally**

---

Laravel Job Integration
-----------------------

[](#laravel-job-integration)

Your Laravel Job becomes a thin adapter.

```
use Illuminate\Contracts\Queue\ShouldQueue;
use Tetthys\SafeJob\Integration\Laravel\Concerns\HandlesSafeJob;
use Tetthys\SafeJob\Core\Contracts\SafeJob as SafeJobContract;

final class ChargeWalletJob implements ShouldQueue
{
    use HandlesSafeJob;

    public function __construct(
        public string $chargeId
    ) {}

    protected function safeJob(): SafeJobContract
    {
        return new ChargeWalletSafeJob($this->chargeId);
    }
}
```

That is all.

- No manual locking
- No duplicate guards
- No custom retry logic

---

Failure Handling (Optional)
---------------------------

[](#failure-handling-optional)

By default, **no failure is finalized**. All exceptions bubble up and Laravel retries normally.

You may define a `FailurePolicy` to explicitly finalize certain errors:

```
protected function failurePolicy(): FailurePolicy
{
    return new class implements FailurePolicy {
        public function finalFailureFor(\Throwable $e): ?FinalFailure
        {
            if ($e instanceof BusinessRuleViolation) {
                return new FinalFailure(
                    code: 'rule_violation',
                    message: $e->getMessage()
                );
            }
            return null;
        }
    };
}
```

Only when `FinalFailure` is returned will the job be permanently marked as **FAILED**.

---

Guarantees
----------

[](#guarantees)

SafeJob provides:

- ✅ Global **exactly-once success**
- ✅ Safe retries under crashes
- ✅ Duplicate dispatch protection
- ✅ Lease-based concurrency control
- ✅ Deterministic final state (SUCCEEDED / FAILED)

What it does **not** do:

- ❌ Distributed transactions
- ❌ Automatic idempotency inside your domain logic

---

When to Use
-----------

[](#when-to-use)

SafeJob is ideal for:

- Payments / Wallets / Escrow
- External API side effects
- Event publishing
- Financial or legal workflows
- Any job where “running twice” is unacceptable

---

Design Philosophy
-----------------

[](#design-philosophy)

> **Jobs must be retryable. Side effects must be final. The boundary must be explicit.**

SafeJob enforces this boundary by design.

---

License
-------

[](#license)

MIT

###  Health Score

34

—

LowBetter than 77% of packages

Maintenance81

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity41

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

130d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/858f92afec0ff81e6888c9ae6f363b56ebf82e45a32d9e1b37341569c5d1b267?d=identicon)[tetthys](/maintainers/tetthys)

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/tetthys-safejob/health.svg)

```
[![Health](https://phpackages.com/badges/tetthys-safejob/health.svg)](https://phpackages.com/packages/tetthys-safejob)
```

###  Alternatives

[barryvdh/laravel-ide-helper

Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.

14.9k123.0M681](/packages/barryvdh-laravel-ide-helper)[illuminate/queue

The Illuminate Queue package.

20331.4M1.2k](/packages/illuminate-queue)[spatie/laravel-health

Monitor the health of a Laravel application

85810.0M82](/packages/spatie-laravel-health)[fumeapp/modeltyper

Generate TypeScript interfaces from Laravel Models

196277.9k](/packages/fumeapp-modeltyper)[orchestra/canvas

Code Generators for Laravel Applications and Packages

21017.2M157](/packages/orchestra-canvas)[clickbar/laravel-magellan

This package provides functionality for working with the postgis extension in Laravel.

423715.4k1](/packages/clickbar-laravel-magellan)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
