NAME

Punk::App - the per-application registry and boot compiler

DESCRIPTION

use Punk; creates one of these per application class. The DSL keywords and plugins record into it; to_app calls "compile", which resolves every target string, flattens guard chains, freezes the route tables and returns the PSGI coderef. Everything wrong croaks at boot; the returned closure is the only code on the request path.

THE REGISTRAR SURFACE

Plugins receive this object; each method mirrors a DSL keyword: route, under, api, docs, static, mount, websocket, sse, session, logging, views, database, model_class, hook, middleware, on_error, on_not_found, helper, plugin, config, secret, host, favicon.

$app->host with no argument reads the declared origin back (undef when the application never declared one), which is how a plugin defaults its own base-URL option; a plugin supporting older Punk should guard with $app->can('host'). install_kw gives a plugin a keyword of its own. model_auto toggles auto-discovery of MyApp::Model::* (on unless models are named explicitly). caller_class and config_object give a plugin the app's controller namespace and its Punk::Config; new and the compile-time helpers (compile, model_instance, render_view) are called by the framework, not apps.

helper

$app->helper(rid => sub { my ($c, @args) = @_; ... });

Installed as a real method on the application's context subclass at compile time. Collisions with core context methods or another helper croak, naming both owners.

env

The application environment, resolved once at compile: the loaded config's env when there is one, else PUNK_ENV, else production - the same safe default Punk::Config and punk use. Development is opted into: punk dev sets it for its server, or set PUNK_ENV=development yourself.

compile_extras

A boot hook: compile calls it just before the compiled state freezes, after the router and hooks are assembled. The framework's own extras live here (the Punk::DevError wiring in development), so a subclass that overrides it must call SUPER::compile_extras.

install_kw

$app->install_kw(task => sub { my ($name, $target) = @_; ... },
                 __PACKAGE__);

Installs a declaration keyword into the application class - how a plugin adds to the DSL without assigning to a glob. The keyword is a magic CV named for the class it lands in; it forwards its arguments to the code and returns what the code returns, in the caller's context.

Installing over a core keyword croaks. Two owners claiming one name croak, naming both, as helpers do; the same owner installing twice is a no-op, which is what a plugin that installs from both import and register needs. Chains. See "KEYWORDS OF YOUR OWN" in Punk::Plugin.

log

The application Punk::Logger (cached on the app), for logging outside a request - startup, background work: $app->log->info(...). Its lines have no method or path. See Punk::Logger and the logging keyword.

COMPILE

compile

Freezes the configuration and returns the PSGI app. Dispatch order: before_request hooks (when any are registered - they run before anything is matched, so they are the only phase a 404, a 405 or a mount reaches), static table, PSGI/static-file mounts (longest prefix first), dynamic buckets, then 404/405. Matched requests construct the context, run before_dispatch hooks and the route's frozen guard chain (a reference return short-circuits), call the handler, and coerce the return value:

  • a PSGI triplet passes through untouched;

  • a Punk::Response is finalized;

  • a Future is chained on psgi.nonblocking servers (the server awaits it) and awaited inline on blocking ones;

  • anything else is JSON-encoded as 200 application/json, folding in any status/headers set through the context;

  • a die runs on_error, then answers 500 {"errors":[{"message":...}]}.

"Construct the context" happens once per request, not once per phase: when a before_request hook has already built one, the routed match is stored into it rather than a second context being made, so a stash written before routing is the same stash the handler reads.

after_dispatch hooks see the finalized triplet (mutate it, or return a replacement); HEAD responses are stripped of their body.

AUTHOR

LNATION <email@lnation.org>

LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION <email@lnation.org>.

This is free software, licensed under:

The Artistic License 2.0 (GPL Compatible)