gplanchat / durable-bridge-illuminate
Illuminate (Laravel) journal, metadata and run stores for gplanchat/durable — durable execution on one SQL database through Laravel's database layer
Package info
github.com/gplanchat/durable-bridge-illuminate
pkg:composer/gplanchat/durable-bridge-illuminate
Requires
- php: >=8.2
- gplanchat/durable: ^0.1.0-beta1
- illuminate/contracts: ^11.0 || ^12.0 || ^13.0
- illuminate/database: ^11.0 || ^12.0 || ^13.0
- illuminate/support: ^11.0 || ^12.0 || ^13.0
Requires (Dev)
None
Suggests
- illuminate/cache: Required by Queue\ResumeLock: a cache store that locks across processes — database, redis, memcached, dynamodb or file. Never array (per-process) or null (locks nothing): both implement LockProvider and neither excludes.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-09 12:56:28 UTC
README
Durable execution on a Laravel database connection.
Read-only mirror. This repository is a subtree-split of gplanchat/durable-dev, published so Composer can require this package on its own. Issues and pull requests are disabled here — open them on the monorepo.
The tests are in the monorepo, not here. This split carries source only. What covers it is
tests/unit/Bridge/Illuminate/in the monorepo, run by itsunitsuite.Documentation: durable.rocks.
Four stores behind the four storage ports, written against Illuminate\Database\Connection — the
query builder, not Eloquent. No Doctrine anywhere in the tree.
| Port | This package |
|---|---|
EventStoreInterface |
IlluminateEventStore |
WorkflowMetadataStore |
IlluminateWorkflowMetadataStore |
ChildWorkflowParentLinkStoreInterface |
IlluminateChildWorkflowParentLinkStore |
WorkflowRunCatalogInterface (+ WorkflowRunProjectionInterface) |
IlluminateWorkflowRunCatalog |
Give the stores a connection of their own
DUR030 sells durable execution on one database with no cluster. It does not need, and Durable does not offer, a transaction shared with business code. Pass these stores a connection of their own, not the application's default one (DUR054): on a shared connection, the transactions the event store opens to fence a pass (DUR053) nest inside the application's, so a business rollback erases journal events and a claim stays invisible to the other workers until the business code commits.
An activity that writes and then dies runs again on replay. The answer is to make it idempotent.
Why the query builder and not Eloquent
Journal rows are append-only facts: execution_id, event_type, a JSON payload, recorded_at.
DB::table() writes them directly. An ActiveRecord model would add identity, events, casts and
timestamps over a row whose entire contract is that it is never mutated.
What proves it
Every store extends its conformance suite from DUR041 — the same cases the in-memory reference
and the DBAL bridge run. The journal suite joins the replay tier: a real workflow with an
activity, a timer and two side effects runs against this store and against InMemoryEventStore,
and what replay reads back is compared. A payload deformed by a round trip breaks replay silently,
not at write time, and that comparison is what catches it.
tests/unit/Bridge/Illuminate/ — 44 cases, four ports
Not in this package
-
A queue, and jobs to put on it. The exclusion between two workers,
Queue\ResumeLock, is ingplanchat/durable-laravel, and so are the jobs. Nothing here decides which queue. -
The Durable service provider. Registering stores, binding ports, adding worker commands — that belongs to the Laravel integration package. A set of stores does not decide how an application wires them.
The provider this package does ship,
DurableIlluminateServiceProvider, registers nothing. It does the one thing no other package can do for it: tell Laravel where its migrations are.
One resume at a time
Queue\ResumeLock, the per-execution lock that keeps two workers from resuming the same execution,
lives in gplanchat/durable-laravel (Gplanchat\Durable\Laravel\Queue\ResumeLock), which uses it on every
backend. If you wire these stores yourself, require that package for the lock.
Install
composer require gplanchat/durable gplanchat/durable-bridge-illuminate php artisan migrate
The four tables ship as a migration, loaded straight from the package — migrate is enough.
Publishing is for when you want to edit them, and from that point they are yours:
php artisan vendor:publish --tag=durable-migrations
Keep the published file's name. Laravel keys migrations by basename and lets
database/migrations win the tie — that shadowing is exactly what makes your copy the one that
runs. Rename it and the two become two migrations, both run, and the second fails on
table "durable_events" already exists with the database half migrated.
Schema\DurableSchema still creates the tables on demand. That is what a test and a worker booting
on an empty database use; an application uses the migration. Two ways to create the same four
tables is two chances to drift, so MigrationMatchesSchemaTest renders the DDL of both — against
MySQL's grammar, which carries lengths and indexes where SQLite discards them — and compares them
statement for statement.
MIT. See LICENSE.