Async-Redis

 view release on metacpan or  search on metacpan

examples/pagi-chat/lib/ChatApp/State.pm  view on Meta::CPAN

    $data->{rooms} = $data->{rooms} ? $JSON->decode($data->{rooms}) : {};
    $data->{connected} = $data->{connected} ? 1 : 0;

    return $data;
}

async sub get_session_by_name {
    my ($name) = @_;

    # Scan for session with this name (not efficient, but works for demo)
    my $cursor = "0";
    do {
        my $result = await $redis->scan($cursor, MATCH => 'session:*', COUNT => 100);
        $cursor = $result->[0];
        my $keys = $result->[1] // [];

        for my $key (@$keys) {
            my $session = await $redis->hgetall($key);
            if ($session && $session->{name} eq $name && $session->{connected}) {
                $session->{rooms} = $session->{rooms} ? $JSON->decode($session->{rooms}) : {};
                return $session;
            }
        }
    } while ($cursor && $cursor ne "0");

    return;
}

async sub create_session {
    my ($session_id, $name, $send_cb) = @_;

    my $session = {
        id        => $session_id,
        name      => $name,

examples/pagi-chat/public/css/style.css  view on Meta::CPAN

/* Buttons */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.75rem 1.5rem;
    border: none;
    border-radius: 8px;
    font-size: 1rem;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.2s;
}

.btn-primary {
    background: var(--accent-color);
    color: white;
}

.btn-primary:hover {
    background: var(--accent-hover);

examples/pagi-chat/public/css/style.css  view on Meta::CPAN

}

.btn-icon {
    width: 32px;
    height: 32px;
    padding: 0;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--text-secondary);
    cursor: pointer;
    font-size: 1.2rem;
    transition: all 0.2s;
}

.btn-icon:hover {
    background: var(--bg-tertiary);
    color: var(--text-primary);
}

.pagi-info {

examples/pagi-chat/public/css/style.css  view on Meta::CPAN

/* Navigation Lists */
.nav-list {
    list-style: none;
    max-height: 200px;
    overflow-y: auto;
}

.nav-list li {
    padding: 0.5rem 0.75rem;
    border-radius: 6px;
    cursor: pointer;
    transition: background 0.2s;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.nav-list li:hover {
    background: var(--bg-tertiary);
}

lib/Async/Redis/Iterator.pm  view on Meta::CPAN

sub new {
    my ($class, %args) = @_;

    return bless {
        redis   => $args{redis},
        command => $args{command} // 'SCAN',
        key     => $args{key},        # For HSCAN/SSCAN/ZSCAN
        match   => $args{match},
        count   => $args{count},
        type    => $args{type},       # For SCAN TYPE filter
        cursor  => 0,
        done    => 0,
    }, $class;
}

async sub next {
    my ($self) = @_;

    # Already exhausted
    return undef if $self->{done};

    my @args;

    # Build command args based on scan type
    if ($self->{command} eq 'SCAN') {
        @args = ($self->{cursor});
    }
    else {
        # HSCAN, SSCAN, ZSCAN take key first, then cursor
        @args = ($self->{key}, $self->{cursor});
    }

    # Add MATCH pattern if specified
    if (defined $self->{match}) {
        push @args, 'MATCH', $self->{match};
    }

    # Add COUNT hint if specified
    if (defined $self->{count}) {
        push @args, 'COUNT', $self->{count};
    }

    # Add TYPE filter for SCAN (Redis 6.0+)
    if ($self->{command} eq 'SCAN' && defined $self->{type}) {
        push @args, 'TYPE', $self->{type};
    }

    # Execute scan command
    my $result = await $self->{redis}->command($self->{command}, @args);

    # Result is [cursor, [elements...]]
    my ($new_cursor, $elements) = @$result;

    # Update cursor
    $self->{cursor} = $new_cursor;

    # Check if iteration complete (cursor returned to 0)
    if ($new_cursor eq '0' || $new_cursor == 0) {
        $self->{done} = 1;
    }

    # Return batch (may be empty)
    # Return undef only when done AND no elements in final batch
    return $elements && @$elements ? $elements : ($self->{done} ? undef : []);
}

sub reset {
    my ($self) = @_;
    $self->{cursor} = 0;
    $self->{done} = 0;
}

sub cursor { shift->{cursor} }
sub done   { shift->{done} }

1;

__END__

=head1 NAME

Async::Redis::Iterator - Cursor-based SCAN iterator

lib/Async/Redis/Iterator.pm  view on Meta::CPAN

    my $iter = $redis->scan_iter(match => 'user:*', count => 100);

    while (my $batch = await $iter->next) {
        for my $key (@$batch) {
            say $key;
        }
    }

=head1 DESCRIPTION

Iterator provides async cursor-based iteration over Redis SCAN commands:

=over 4

=item * C<SCAN> - iterate keys

=item * C<HSCAN> - iterate hash field/value pairs

=item * C<SSCAN> - iterate set members

=item * C<ZSCAN> - iterate sorted set members and scores as a flat list

lib/Async/Redis/Iterator.pm  view on Meta::CPAN

    my $batch = await $iter->next;

Return the next batch as an arrayref, or C<undef> when the scan is complete and
there are no elements in the final batch. Redis may return empty intermediate
batches; those are returned as empty arrayrefs and are still truthy in Perl.

=head2 reset

    $iter->reset;

Reset the cursor to zero so iteration can start again.

=head2 cursor

Return the current Redis cursor.

=head2 done

Return true after Redis has returned cursor C<0>.

=head1 BEHAVIOR

=over 4

=item * Returns batches of elements, not individual items

=item * Cursor managed internally

=item * C<next> returns C<undef> when iteration is complete

lib/Async/Redis/KeyExtractor.pm  view on Meta::CPAN

    'GEOSEARCHSTORE' => sub { (0, 1) },

    # MIGRATE - special handling
    'MIGRATE'   => \&_keys_for_migrate,

    # SORT
    'SORT'      => sub { (0) },
    'SORT_RO'   => sub { (0) },

    # SCAN commands return patterns, not keys - first arg is key for HSCAN/SSCAN/ZSCAN
    'SCAN'      => sub { () },  # No key, cursor-based

    # Pub/Sub - channels, not keys
    'PUBLISH'   => sub { () },
    'SUBSCRIBE' => sub { () },
    'UNSUBSCRIBE' => sub { () },
    'PSUBSCRIBE' => sub { () },
    'PUNSUBSCRIBE' => sub { () },

    # Server commands - no keys
    'PING'      => sub { () },

script/commands.json  view on Meta::CPAN

            "readonly"
        ],
        "hints": [
            "nondeterministic_output"
        ]
    },
    "HSCAN": {
        "summary": "Iterates over fields and values of a hash.",
        "since": "2.8.0",
        "group": "hash",
        "complexity": "O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.",
        "acl_categories": [
            "@read",
            "@hash",
            "@slow"
        ],
        "arity": -3,
        "key_specs": [
            {
                "begin_search": {
                    "type": "index",

script/commands.json  view on Meta::CPAN

            }
        ],
        "arguments": [
            {
                "name": "key",
                "type": "key",
                "display_text": "key",
                "key_spec_index": 0
            },
            {
                "name": "cursor",
                "type": "integer",
                "display_text": "cursor"
            },
            {
                "name": "pattern",
                "type": "pattern",
                "display_text": "pattern",
                "token": "MATCH",
                "optional": true
            },
            {
                "name": "count",

script/commands.json  view on Meta::CPAN

            "admin",
            "noscript",
            "no_async_loading",
            "no_multi"
        ]
    },
    "SCAN": {
        "summary": "Iterates over the key names in the database.",
        "since": "2.8.0",
        "group": "generic",
        "complexity": "O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.",
        "history": [
            [
                "6.0.0",
                "Added the `TYPE` subcommand."
            ]
        ],
        "acl_categories": [
            "@keyspace",
            "@read",
            "@slow"
        ],
        "arity": -2,
        "arguments": [
            {
                "name": "cursor",
                "type": "integer",
                "display_text": "cursor"
            },
            {
                "name": "pattern",
                "type": "pattern",
                "display_text": "pattern",
                "token": "MATCH",
                "optional": true
            },
            {
                "name": "count",

script/commands.json  view on Meta::CPAN

        ],
        "command_flags": [
            "write",
            "fast"
        ]
    },
    "SSCAN": {
        "summary": "Iterates over members of a set.",
        "since": "2.8.0",
        "group": "set",
        "complexity": "O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.",
        "acl_categories": [
            "@read",
            "@set",
            "@slow"
        ],
        "arity": -3,
        "key_specs": [
            {
                "begin_search": {
                    "type": "index",

script/commands.json  view on Meta::CPAN

            }
        ],
        "arguments": [
            {
                "name": "key",
                "type": "key",
                "display_text": "key",
                "key_spec_index": 0
            },
            {
                "name": "cursor",
                "type": "integer",
                "display_text": "cursor"
            },
            {
                "name": "pattern",
                "type": "pattern",
                "display_text": "pattern",
                "token": "MATCH",
                "optional": true
            },
            {
                "name": "count",

script/commands.json  view on Meta::CPAN

        ],
        "command_flags": [
            "readonly",
            "fast"
        ]
    },
    "ZSCAN": {
        "summary": "Iterates over members and scores of a sorted set.",
        "since": "2.8.0",
        "group": "sorted-set",
        "complexity": "O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.",
        "acl_categories": [
            "@read",
            "@sortedset",
            "@slow"
        ],
        "arity": -3,
        "key_specs": [
            {
                "begin_search": {
                    "type": "index",

script/commands.json  view on Meta::CPAN

            }
        ],
        "arguments": [
            {
                "name": "key",
                "type": "key",
                "display_text": "key",
                "key_spec_index": 0
            },
            {
                "name": "cursor",
                "type": "integer",
                "display_text": "cursor"
            },
            {
                "name": "pattern",
                "type": "pattern",
                "display_text": "pattern",
                "token": "MATCH",
                "optional": true
            },
            {
                "name": "count",



( run in 1.713 second using v1.01-cache-2.11-cpan-39bf76dae61 )