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 )