Plugins
A plugin packages behaviour an application opts into with one line:
plugin 'RequestId';
plugin '+My::Plugin' => { opt => 1 };
A name resolves against Punk::Plugin::; the + prefix takes the
class literally. The plugin's register receives the application
registrar and its options, and wires whatever it needs - routes, hooks,
helpers, keywords.
Helpers
The lightest extension is a context helper - a new method every handler can call:
helper uid => sub { my ($c) = @_; $c->stash->{uid} };
# later, anywhere
my $uid = $c->uid;
Plugins usually install these from register.
Hooks
hook before_dispatch => sub { my ($c) = @_; ...; return };
hook after_dispatch => sub { my ($c, $resp) = @_; ... };
before_dispatch runs after routing, before guards; a reference return
short-circuits the request. after_dispatch sees the finalized PSGI
triplet and may mutate or replace it. A request-id plugin is ten lines:
stamp the context in before_dispatch, stamp the header in
after_dispatch.
Middleware
For behaviour that must wrap the whole PSGI app - compression, raw timing - there is the outer layer:
middleware sub {
my ($app) = @_;
sub { my ($env) = @_; ...; $app->($env) };
};
Keywords of your own
A plugin can install declaration keywords into the application class, so its surface reads like the core DSL:
$app->install_kw(queue => sub { ... });
Punk installs the keyword from C beside its own: installing over a core
keyword croaks, two owners claiming one name croak naming both, and the
same owner installing twice is a no-op. The keyword forwards its
arguments and returns in the caller's context - my $q = queue(); is a
keyword call like any other.
This is how Punk::Queue gives an application
queue, task and cron that look and behave exactly like get and
under:
use Punk::Plugin::Queue; # compile time: the keywords exist
plugin 'Queue' => { dsn => ... }; # runtime: the configuration
task 'mail.send' => 'Job::Mail#send';
What a plugin sees
register receives the Punk::App registrar - the same surface the
keywords write to - so a plugin can declare routes, guards, mounts,
helpers, hooks and keywords with no privileged API. If the application
class could write it, the plugin can.
What ships
Punk itself carries nine plugins, each documented where its subject lives:
RequestId,Metrics,Health- ObservabilityCSP- Sessions and securityBlob- Uploads and filesIdempotency- CachingConditionalGet,Sitemap- SEO and HTTP cachingI18n- I18n
And five ship as their own distributions, because they carry an engine or a client the core does not need: Punk::Plugin::Queue, Punk::Plugin::Mailer, Punk::Plugin::TOTP, Punk::Plugin::ClamAV and Punk::Plugin::OpenTelemetry.