Hypersonic

 view release on metacpan or  search on metacpan

lib/Hypersonic/SSE.pm  view on Meta::CPAN

package Hypersonic::SSE;
use strict;
use warnings;
use 5.010;

# Hypersonic::SSE - High-level Server-Sent Events API
#
# Wraps the streaming infrastructure to provide a clean SSE interface.
# Automatically handles headers, event formatting, and keepalives.
# Uses JIT-compiled XS for performance.

our $VERSION = '0.19';

use constant {
    STATE_INIT     => 0,
    STATE_STARTED  => 1,
    STATE_FINISHED => 2,
};
use constant MAX_SSE_INSTANCES => 65536;
use constant DEFAULT_KEEPALIVE => 30;

use Hypersonic::Protocol::SSE;

=head1 NAME

Hypersonic::SSE - Server-Sent Events streaming interface

=head1 SYNOPSIS

    $app->get('/events' => sub {
        my ($req, $stream) = @_;

        my $sse = Hypersonic::SSE->new($stream);

        $sse->event(
            type => 'message',
            data => 'Hello World!',
        );

        $sse->event(
            type => 'update',
            data => '{"count": 42}',
            id   => '123',
        );

        $sse->close();
    }, { streaming => 1 });

=head1 DESCRIPTION

Hypersonic::SSE provides a high-level API for sending Server-Sent Events.
It wraps a Hypersonic::Stream object and handles SSE-specific formatting,
headers, and keepalives.

=cut

# ============================================================
# XS Code Generation - ALL instance methods generated in C
# ============================================================

sub generate_c_code {
    my ($class, $builder, $opts) = @_;
    $opts //= {};
    my $max = $opts->{max_sse_instances} // MAX_SSE_INSTANCES;

    $builder->line('#include <time.h>')
      ->blank;

    $class->gen_sse_registry($builder, $max);
    $class->gen_sse_reset($builder);
    $class->gen_sse_format_event($builder);
    $class->gen_sse_format_keepalive($builder);
    $class->gen_sse_format_retry($builder);
    $class->gen_sse_format_comment($builder);

    # XS instance methods
    $class->gen_xs_new($builder);
    $class->gen_xs_stream($builder);
    $class->gen_xs_is_started($builder);
    $class->gen_xs_event_count($builder);
    $class->gen_xs_last_event_time($builder);
    $class->gen_xs_needs_keepalive($builder);
    $class->gen_xs_event($builder);
    $class->gen_xs_data($builder);
    $class->gen_xs_retry($builder);
    $class->gen_xs_keepalive($builder);
    $class->gen_xs_comment($builder);
    $class->gen_xs_close($builder);

    return $builder;
}

sub gen_sse_registry {
    my ($class, $builder, $max) = @_;

    $builder->comment('SSE instance registry - stores SSE state')
      ->line('#define SSE_MAX ' . $max)
      ->line('#define SSE_STATE_INIT     0')
      ->line('#define SSE_STATE_STARTED  1')
      ->line('#define SSE_STATE_FINISHED 2')
      ->blank
      ->line('typedef struct {')
      ->line('    SV* stream_sv;')
      ->line('    int state;')
      ->line('    int event_count;')
      ->line('    time_t last_event_time;')
      ->line('    int keepalive_interval;')

lib/Hypersonic/SSE.pm  view on Meta::CPAN

        'Hypersonic::SSE::is_started'      => { source => 'xs_sse_is_started', is_xs_native => 1 },
        'Hypersonic::SSE::event_count'     => { source => 'xs_sse_event_count', is_xs_native => 1 },
        'Hypersonic::SSE::last_event_time' => { source => 'xs_sse_last_event_time', is_xs_native => 1 },
        'Hypersonic::SSE::needs_keepalive' => { source => 'xs_sse_needs_keepalive', is_xs_native => 1 },
        'Hypersonic::SSE::event'           => { source => 'xs_sse_event', is_xs_native => 1 },
        'Hypersonic::SSE::data'            => { source => 'xs_sse_data', is_xs_native => 1 },
        'Hypersonic::SSE::retry'           => { source => 'xs_sse_retry', is_xs_native => 1 },
        'Hypersonic::SSE::keepalive'       => { source => 'xs_sse_keepalive', is_xs_native => 1 },
        'Hypersonic::SSE::comment'         => { source => 'xs_sse_comment', is_xs_native => 1 },
        'Hypersonic::SSE::close'           => { source => 'xs_sse_close', is_xs_native => 1 },
    };
}

1;

__END__

=head1 CLIENT EXAMPLE

JavaScript:

    const events = new EventSource('/events');
    
    events.onmessage = (e) => {
        console.log('Message:', e.data);
    };
    
    events.addEventListener('update', (e) => {
        console.log('Update:', JSON.parse(e.data));
        console.log('Event ID:', e.lastEventId);
    });
    
    events.onerror = (e) => {
        if (e.target.readyState === EventSource.CLOSED) {
            console.log('Connection closed');
        } else {
            console.log('Error, will auto-reconnect');
        }
    };

=head1 RECONNECTION

The browser automatically reconnects when the connection drops.
Use the C<id> field to enable resumption:

    $app->get('/events' => sub {
        my ($req, $stream) = @_;
        
        my $last_id = $req->header('Last-Event-ID') // 0;
        my $sse = Hypersonic::SSE->new($stream);
        
        for my $id (($last_id + 1) .. 100) {
            $sse->event(
                type => 'update',
                data => "Event $id",
                id   => $id,
            );
        }
        
        $sse->close();
    }, { streaming => 1 });

=head1 SEE ALSO

L<Hypersonic::Stream>, L<Hypersonic::Protocol::SSE>

=head1 AUTHOR

Hypersonic Contributors

=cut



( run in 0.337 second using v1.01-cache-2.11-cpan-140bd7fdf52 )