Lakutata Application
The application is the root object of a Lakutata program. Application.run() creates it in a new root container, registers the objects it declares, runs its bootstrap steps, and ends it gracefully on a shutdown signal. This page covers its options, the fluent API, the lifecycle, the mode and packed applications. The objects it creates are described in Objects, and how they obtain one another in Injection.
1. Options
Application.run(options) — or run(() => options), a function called at launch that may be async — takes these options:
| Option | Meaning |
|---|---|
id, name | Required. The ID (also the process.title) and the name of the application (app.appId, app.appName) |
timezone | An IANA time zone, or 'auto' (default) for the system's; written to TZ |
mode | 'development' or 'production', when the environment gives none (see section 4) |
shutdownSignals | The signals that end the application gracefully (default ['SIGTERM', 'SIGINT']), or false |
components, providers | The components and providers by name: {class, ...options} |
objects | Other objects: {named: {...}, anonymous: [...]} |
bootstrap | The steps run in order at launch: a name, a symbol or a class (the object is created), or an async function that receives the application |
checkOptions | Check the options of the declared objects at launch (default true) |
A declaration without class adds options to an object of the same name that the framework presets: the lakutata package presets log (the Logger) and entrypoint (the Entrypoint), so log: {level: 'debug'} sets the level of the preset logger. Application.preset(options) presets objects for every application in the process.
2. The fluent API
import {Application, type ApplicationExitReason, type ILogger} from 'lakutata'
Application
//Environment variables, set at launch before the options function runs
.env({TZ_NAME: 'Asia/Shanghai'})
//Path aliases: '@data' in a path passed to path.resolve() or app.alias.resolve(); the directory is created when missing
.alias({'@data': './data'}, true)
.run(() => ({
id: 'com.example.app',
name: 'Example',
components: {}
}))
//Called once the bootstrap has completed (not awaited)
.onLaunched((app: Application, log: ILogger): void => log.info('%s launched in %s mode', app.appName, app.mode()))
//Called and awaited when the application exits, before its objects are destroyed
.onDone(async (app: Application, log: ILogger, reason: ApplicationExitReason): Promise<void> => {
log.info('%s done (exit code %d%s)', app.appName, reason.code, reason.signal ? `, ${reason.signal}` : '')
})
//An uncaught exception in the process; the application keeps running
.onUncaughtException((error: Error, log: ILogger): void => log.warn(error.message))
//The launch failed: the returned number is the exit code (1 when none)
.onFatalException((error: Error, log: ILogger): number => {
log.error('Launch failed: %s', error.message)
return 100
})app.exit(code) ends the application gracefully with that exit code; app.exit(true) ends the process immediately with exit code 2, skipping the done handlers and the teardown.
3. The lifecycle
- The environment variables are set and the aliases registered (
@runtimeis the working directory). - The options are read and validated; the ID, the time zone and the mode are applied.
- The application is created in a new root container: its objects are registered and its
bootstrapruns in order. - The
launched()hooks of the objects and theonLaunchedhandlers run.
- Launch failure: when the application cannot launch (invalid options, an object that fails to initialize, a bootstrap step that fails), the
onFatalExceptionhandler decides the exit code (1 when it returns none). Without a handler, the error is logged and the process exits with code 1, as Node.js does on an uncaught exception, so whatever runs it (Docker restart policies, Kubernetes, systemd, CI) knows it failed; objects already created (a listening server, a connection) do not keep the process alive. - Shutdown signals: on
SIGTERM(docker stop, Kubernetes rolling updates, systemd) andSIGINT(Ctrl+C), the application ends gracefully. TheonDonehandlers run first, while the application is still fully working (their third argument says why it is exiting:{code: 0, signal: 'SIGTERM'}, or{code}afterapp.exit(code)). Then the entrypoints stop accepting requests and let the running ones finish, the objects are destroyed, and the process exits with code 0. A second signal (if the shutdown hangs), or a signal received while the application is still launching, ends the process at once with code 128 + the signal number (143 forSIGTERM, 130 forSIGINT). TheshutdownSignalsoption accepts other signals (['SIGTERM', 'SIGINT', 'SIGHUP']), orfalseto leave them to Node.js. - Nothing left to do: when nothing keeps the process busy any more (no server, connection or timer), the application ends gracefully, as with
app.exit(0): theonDonehandlers run, the objects are destroyed, and the process exits with the code it has (process.exitCode, 0 by default). A script can therefore just finish its work. An application that has been disposed (app.dispose(), as in tests) leaves the end of the process to whatever runs it.
4. The mode
app.mode() returns 'development' or 'production'. The environment overrides the code, so the same build can run in either mode: the MODE environment variable wins, then NODE_ENV, then the mode option, and 'development' otherwise. A MODE with an unknown value is ignored with a warning; a NODE_ENV with an unknown value (such as test) is skipped. The resolved mode is written back to MODE and NODE_ENV, so libraries see the same mode. The variables can come from the system, from Application.env({MODE: 'production'}), or from the .env file that Bun reads (when running bun App.js; executables built by Bun do not read it).
5. The packed application
lakutata package bundle, package exe and package installer pack the compiled application into a bundle, an executable or installers. When it runs packed, isPackaged() (from lakutata) returns true, and resourcesPath() returns the directory of its resources, the files declared in the lakutata.resources of its package.json; when not packed, it returns undefined and the application reads those files from where they are built. See Packaging.
6. Entrypoints
The entrypoint component (lakutata/com/entrypoint) serves the controllers over HTTP, the CLI, service transports, schedules and message queues. Configure it with buildEntrypoints({controllers, http, cli, service, schedule, queue}) and list it in bootstrap; see Entrypoint.