Lines 100.00% 14 / 14
Functions and Methods 100.00% 2 / 2
Classes and Traits 100.00% 1 / 1
Name Lines Functions and Methods CRAP Classes and Traits
DebounceOperator 100.00% 14 / 14 100.00% 2 / 2 6 100.00% 1 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 attach 100.00% 13 / 13 100.00% 1 / 1 5
1<?php
2
3declare(strict_types=1);
4
5namespace Stewart\Support\Stream;
6
7use Closure;
8use Stewart\Contracts\Exception\TimeException;
9use Stewart\Contracts\Stream\Edge;
10use Stewart\Contracts\Subscription;
11use Stewart\Contracts\Time\Duration;
12use Stewart\Contracts\Time\Timers;
13
14/**
15 * @internal
16 * @template T
17 * @implements StreamSource<T>
18 */
19final readonly class DebounceOperator implements StreamSource
20{
21    /**
22     * @param StreamSource<T> $source
23     * @param Closure(T): string $partition
24     * @throws TimeException
25     */
26    public function __construct(
27        private Timers $timers,
28        private StreamSource $source,
29        private Duration $window,
30        private Edge $edge,
31        private Closure $partition,
32    ) {
33        $window->requireAtLeastOneMillisecond('debounce');
34    }
35
36    public function attach(SubscriptionScope $scope, Closure $downstream): Subscription
37    {
38        $edge = $this->edge;
39        $partition = $this->partition;
40        /** @var PendingEmissions<T> $pendings */
41        $pendings = new PendingEmissions($this->timers, $this->window, $scope, $scope->claimStage(), $downstream);
42
43        $scope->onTeardown($pendings->disarmAll(...));
44
45        return $this->source->attach($scope, static function (mixed $event) use ($pendings, $partition, $edge, $downstream): void {
46            $key = $partition($event);
47            $leads = !$pendings->isArmed($key) && $edge->emitsLeading();
48
49            if (!$leads && $edge->emitsTrailing()) {
50                $pendings->hold($key, $event);
51            }
52
53            $pendings->arm($key);
54
55            if ($leads) {
56                $downstream($event);
57            }
58        });
59    }
60}