Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c3ec338
docs(queue): refresh transactional outbox implementation plan
binaryfire Oct 22, 2026
0e726ce
Clarify targeted verification after failures
binaryfire Oct 22, 2026
b645321
fix(database): track transactions by resolved connection identity
binaryfire Oct 22, 2026
fca61fb
feat(bus): capture context for dispatch lock cleanup
binaryfire Oct 22, 2026
0c977ac
feat(bus): add opt-in transactional outbox dispatch options
binaryfire Oct 22, 2026
2e3b848
feat(queue): define raw outbox publication contracts
binaryfire Oct 22, 2026
48db287
feat(queue): stage transactional outbox jobs and publish raw batches
binaryfire Oct 22, 2026
e083850
feat(horizon): preserve queue lifecycle across outbox publication
binaryfire Oct 22, 2026
92b2c86
perf(horizon): skip monitored-tag reads for untagged jobs
binaryfire Oct 22, 2026
c2e7eac
docs(queue): record outbox checkpoint contracts and remaining work
binaryfire Oct 22, 2026
4af33e2
fix(cache): preserve named repository identity through custom and mem…
binaryfire Oct 22, 2026
d441373
feat(queue): relay outbox batches through bounded resource lifetimes
binaryfire Oct 22, 2026
957b82e
feat(queue): coordinate concurrent outbox delivery with graceful shut…
binaryfire Oct 22, 2026
fb54177
feat(queue): add relay and transactional outbox operator commands
binaryfire Oct 22, 2026
043e348
feat(opentelemetry): observe outbox delivery at batch boundaries
binaryfire Oct 22, 2026
f9da181
docs(queue): align outbox plan with relay and operations contracts
binaryfire Oct 22, 2026
343a6be
test(queue): pin set-based outbox staging and settlement
binaryfire Oct 22, 2026
8996a5a
test(queue): verify outbox claims on supported database engines
binaryfire Oct 22, 2026
a9f3aaf
test(queue): exercise outbox producers and Redis worker delivery
binaryfire Oct 22, 2026
bf01122
docs(queue): document transactional outbox setup and operations
binaryfire Oct 22, 2026
920c0b1
docs(opentelemetry): explain transactional outbox delivery metrics
binaryfire Oct 22, 2026
85f7979
perf(queue): add repeatable transactional outbox workload harness
binaryfire Oct 22, 2026
cb5f960
docs(queue): record completed outbox implementation and measurement s…
binaryfire Oct 22, 2026
910e55a
docs(queue): clarify outbox trait property defaults
binaryfire Oct 23, 2026
29a8f84
docs(queue): align plan guidance for outbox trait properties
binaryfire Oct 23, 2026
4eed24f
perf(queue): retire the measured reservation comparison
binaryfire Oct 23, 2026
18e0de1
docs(queue): record outbox performance measurements
binaryfire Oct 23, 2026
529cea1
docs(queue): complete outbox measurement plan
binaryfire Oct 23, 2026
294052a
test(queue): account for runner memory in outbox relay tests
binaryfire Oct 23, 2026
a5cd417
fix(bus): validate batch outbox ownership before dispatch
binaryfire Oct 23, 2026
0d1cdb9
fix(queue): preserve immediate local outbox dispatch results
binaryfire Oct 23, 2026
81657ad
docs(queue): clarify batch outbox defaults and ownership
binaryfire Oct 23, 2026
2f11e2e
docs(queue): align outbox guidance with Laravel documentation style
binaryfire Oct 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ During implementation, run new or changed test files immediately. After completi

Use checks that match the change. For isolated changes, run `composer lint:fix`, `composer analyse`, and the affected tests. Run a single affected test file with PHPUnit. Use ParaTest when the affected tests span multiple files. Only run `composer fix` when changes could affect code beyond the affected tests; it already runs formatting, analysis, and all test suites, so do not run those checks separately first.

If a check fails, use targeted checks while correcting the issue, then run the failed check and each remaining check. Rerun an earlier check only if the correction could affect it.
After a failure, fix the cause and rerun only affected tests, formatting or static analysis. Then finish any verification not yet run. Repeat the full test suite only if the fix itself has framework-wide impact.

## Development Conventions

Expand Down

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions src/broadcasting/src/BroadcastEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Hypervel\Bus\Queueable;
use Hypervel\Contracts\Broadcasting\Factory as BroadcastingFactory;
use Hypervel\Contracts\Queue\ShouldQueue;
use Hypervel\Contracts\Queue\ShouldQueueViaOutbox;
use Hypervel\Contracts\Support\Arrayable;
use Hypervel\Queue\Attributes\Backoff;
use Hypervel\Queue\Attributes\DeleteWhenMissingModels;
Expand Down Expand Up @@ -67,6 +68,17 @@ public function __construct(mixed $event)
$this->timeout = $this->getAttributeValue($event, Timeout::class, 'timeout');
$this->backoff = $this->getAttributeValue($event, Backoff::class, 'backoff');
$this->afterCommit = property_exists($event, 'afterCommit') ? $event->afterCommit : null;

if (isset($event->viaOutbox)) {
$this->viaOutbox = $event->viaOutbox;
} elseif ($event instanceof ShouldQueueViaOutbox) {
$this->viaOutbox = true;
}

if (isset($event->outboxDatabase)) {
$this->outboxDatabase = $event->outboxDatabase;
}

$this->maxExceptions = $this->getAttributeValue($event, MaxExceptions::class, 'maxExceptions');
$this->deleteWhenMissingModels = $this->getAttributeValue($event, DeleteWhenMissingModels::class, 'deleteWhenMissingModels', $this->deleteWhenMissingModels);
}
Expand Down Expand Up @@ -115,6 +127,10 @@ protected function getPayloadFromEvent(mixed $event): array
$payload = [];

foreach ((new ReflectionClass($event))->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
if (! $property->isInitialized($event)) {
continue;
}

$payload[$property->getName()] = $this->formatProperty($property->getValue($event));
}

Expand Down
38 changes: 34 additions & 4 deletions src/bus/src/Batch.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
use Hypervel\Contracts\Events\Dispatcher;
use Hypervel\Contracts\Queue\Factory as QueueFactory;
use Hypervel\Contracts\Support\Arrayable;
use Hypervel\Database\ConnectionResolverInterface;
use Hypervel\Queue\CallQueuedClosure;
use Hypervel\Support\Arr;
use Hypervel\Support\CarbonImmutable;
use Hypervel\Support\Collection;
use Hypervel\Support\Enumerable;
use JsonSerializable;
use LogicException;
use Throwable;

class Batch implements Arrayable, JsonSerializable
Expand Down Expand Up @@ -57,8 +59,10 @@ public function fresh(): ?Batch
public function add(array|object $jobs): ?Batch
{
$count = 0;
$owner = null;
$resolver = null;

$jobs = Collection::wrap($jobs)->map(function ($job) use (&$count) {
$jobs = Collection::wrap($jobs)->map(function ($job) use (&$count, &$owner, &$resolver) {
$job = $job instanceof Closure ? CallQueuedClosure::create($job) : $job;

if (is_array($job)) {
Expand All @@ -76,11 +80,37 @@ public function add(array|object $jobs): ?Batch
$first->allOnConnection($this->options['connection']);
}

return $first->chain($chain->slice(1)->values()->all());
$job = $first->chain($chain->slice(1)->values()->all());
} else {
$job->withBatchId($this->id);

++$count;
}
$job->withBatchId($this->id);

++$count;
if (array_key_exists('viaOutbox', $this->options) || array_key_exists('outboxDatabase', $this->options)) {
// Preserve initialized nulls as well as explicit values on each job.
$properties = get_object_vars($job);

foreach (['viaOutbox', 'outboxDatabase'] as $property) {
if (array_key_exists($property, $this->options) && ! array_key_exists($property, $properties)) {
$job->{$property} = $this->options[$property];
}
}
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
}

// The counter transaction supplies the implicit SQL owner; declared owners must not bypass it.
if (($database = $job->outboxDatabase ?? null) !== null
&& ($job->viaOutbox ?? null) !== false
&& $this->repository instanceof DatabaseBatchRepository) {
$owner ??= $this->repository->getConnection()->getNameWithReadWriteType();
/** @var ConnectionResolverInterface $resolver */
$resolver ??= Container::getInstance()->make('db');
$selectedOwner = $resolver->connection($database)->getNameWithReadWriteType();

if ($selectedOwner !== $owner) {
throw new LogicException("Outbox database [{$selectedOwner}] does not match the batch database [{$owner}]. Use the batch database or omit the explicit outbox owner.");
}
}

return $job;
});
Expand Down
2 changes: 2 additions & 0 deletions src/bus/src/Batchable.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

trait Batchable
{
use InteractsWithQueueOutbox;

/**
* The batch ID (if applicable).
*/
Expand Down
3 changes: 2 additions & 1 deletion src/bus/src/DebounceLock.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,14 @@ public function acquire(mixed $job, ?int $debounceFor = null, ?int $maxWait = nu
public function acquireForDispatch(object $job, ?int $debounceFor = null, ?int $maxWait = null): array
{
[$cache, $key, $ttl, $maxWait] = $this->resolveLockValues($job, $debounceFor, $maxWait);
$releaseContext = DispatchLockContext::captureReleaseContext();
$result = $this->acquireResolvedLock($cache, $key, $ttl, $maxWait);

if (isset(class_uses_recursive($job)[Queueable::class])) {
$job->debounceOwner = $result['owner'];
}

DispatchLockContext::registerDebounce($job, $cache, $key, $result['owner']);
DispatchLockContext::registerDebounce($job, $cache, $key, $result['owner'], $releaseContext);

return $result;
}
Expand Down
Loading
Loading