Skip to content

@lakutata/core ​

The core of the lakutata framework: the IoC container and dependency injection, the object model (BaseObject, Component, Provider, Application) with its asynchronous construction and destruction, and schema validation (DTO, a validation engine with the API of joi), which the @Configurable, @Expect, @Accept and @Return decorators use.

Most applications install the umbrella package lakutata, which re-exports this package (lakutata, lakutata/decorator/di, lakutata/decorator/dto, lakutata/decorator/asst, lakutata/dtos) and presets the log and entrypoint components. @lakutata/core alone is enough for a library, or for an application that needs neither (it then logs to the console).

Installation ​

shell
npm install lakutata          # the framework
npm install @lakutata/core    # or the core only

The package needs Node.js ^22.13.0 || >=24.11.0. Projects use ES modules ("type": "module"); the package also ships CommonJS builds. The decorators need these TypeScript options:

json
{
  "compilerOptions": {
    "module": "Node16",
    "moduleResolution": "Node16",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Usage ​

An application ​

An application is a tree of objects that its container creates. Application.run() validates the application's options, registers its components and providers, creates the objects listed in bootstrap, then calls onLaunched.

typescript
import {Application, Component, DTO, type ILogger} from 'lakutata'
import {Configurable, Inject} from 'lakutata/decorator/di'

//A component: a singleton of the application, created when it is first needed (or by bootstrap)
class Mailer extends Component {
    //An option, validated when the component is created; its value comes from the application options below
    @Configurable(DTO.String().required())
    protected readonly host: string

    @Inject('log')
    protected readonly log: ILogger

    //Called once its injected objects and options are set
    protected async init(): Promise<void> {
        this.log.info('Mailer connected to %s', this.host)
    }

    public async send(to: string, text: string): Promise<void> {
        this.log.info('Mail to %s: %s', to, text)
    }

    //Called when the application stops
    protected async destroy(): Promise<void> {
    }
}

Application
    .run(() => ({
        id: 'com.example.app',
        name: 'Example',
        components: {
            mailer: {class: Mailer, host: 'smtp.example.com'}
        },
        bootstrap: ['mailer']
    }))
    .onLaunched(async (app: Application, log: ILogger): Promise<void> => {
        const mailer: Mailer = await app.getObject('mailer')
        await mailer.send('someone@example.com', 'Hello')
        log.info('%s started', app.appName)
    })
    .onFatalException((error: Error, log: ILogger): void => log.error(error.message))

Lifetimes ​

DecoratorOne instance…Default of
@Singleton()per containerComponent
@Scoped()per scope (container.createScope(), e.g. a request)Provider (the controllers)
@Transient()per resolutionBaseObject

Dependency injection ​

@Inject() injects the object registered under the property's name, @Inject('name') the one registered under another name, and @Inject(SomeClass) an instance of a class (registered automatically when the class is marked @Autoload()). Injected objects are set before init() runs.

Validation ​

A DTO subclass declares its fields with @Expect(schema). Schemas are built with the static methods of DTO (DTO.String(), DTO.Number(), DTO.Object(), DTO.Array(), DTO.Alternatives()…), which follow the joi API:

typescript
import {DTO} from 'lakutata'
import {Accept, Expect} from 'lakutata/decorator/dto'

class CreateUserDTO extends DTO {
    @Expect(DTO.String().email().required())
    public email: string

    @Expect(DTO.Number().integer().min(0).default(0))
    public credit: number
}

class Users {
    //The argument is validated (and its defaults applied) before the method runs
    @Accept(CreateUserDTO.required())
    public async create(input: CreateUserDTO): Promise<void> {
    }
}

//Validated directly: resolves to the value with its defaults ({email: 'a@example.com', credit: 0}), or rejects with
//InvalidValueException when the value is invalid
const input = await CreateUserDTO.validateAsync({email: 'a@example.com'})

Tests and libraries: a container without an application ​

typescript
import {Component, Container, DTO} from 'lakutata'
import {Configurable} from 'lakutata/decorator/di'

class Mailer extends Component {
    @Configurable(DTO.String().required())
    protected readonly host: string

    public async send(to: string, text: string): Promise<string> {
        return `${this.host} -> ${to}: ${text}`
    }
}

//Created with its options; set() resolves once its init() has completed
const container: Container = new Container()
const mailer: Mailer = await container.set(Mailer, {host: 'localhost'})
await mailer.send('someone@example.com', 'Hello')
//Destroys the objects it created
await container.destroy()

API ​

ExportSubpathWhat it is
ApplicationlakutataThe application: run(), env(), alias(), onLaunched(), onDone(), onFatalException(), getObject(), exit()
ModulelakutataThe base class of Application: the container and the declared objects live here
ComponentlakutataA singleton service with events (emit, on…)
ProviderlakutataA scoped object (one per scope)
BaseObjectlakutataThe root class: asynchronous construction (init), destruction (destroy), getObject
ContainerlakutataThe IoC container: set, get, getShared, register, createScope, destroy
DTOlakutataValidated data classes and the schema builders
ExceptionlakutataThe base of the exceptions of the framework (errno, statusCode)
isPackaged, resourcesPathlakutataWhether the application runs packed, and its resources directory
registerValidationMessages, runInValidationLanguage…lakutataThe languages of the validation messages
Inject, Configurable, Autoload, Singleton, Scoped, Transient, Lifetimelakutata/decorator/diDependency injection and lifetimes
Expect, Accept, Return, IndexSignaturelakutata/decorator/dtoDTO fields, method argument and return validation
Before, Afterlakutata/decorator/asstFunctions run before and after a method
PaginationSearchDTO, PaginationResultDTO, paginator, FlexibleDTO, toJsonSchema, fromJsonSchemalakutata/dtosPagination, open DTOs and JSON Schema conversion
ConsoleLogger, ILogger@lakutata/core/com/loggerThe logger interface and its console implementation

The typings document each declaration with examples.

Errors ​

ExceptionThrown when
InvalidValueExceptionA value does not match its schema (DTO.validate, @Configurable, @Expect)
InvalidMethodAcceptException, InvalidMethodReturnExceptionThe arguments (@Accept) or the result (@Return) of a method are invalid
DependencyInjectionExceptionAn injected object cannot be resolved
InvalidObjectOptionsException, InvalidObjectTypeExceptionThe options or the class of a declared object are invalid
LifetimeLockedExceptionThe lifetime of a class is changed after it was locked
ObjectDestroyedExceptionA destroyed object is used
AliasExistsException, AliasNotFoundException, InvalidAliasNameExceptionA path alias is declared twice, missing, or invalid
MethodNotFoundExceptionA method called by name does not exist

See also ​

  • The guides doc/en/Application.md, doc/en/Objects.md, doc/en/Injection.md, doc/en/DTOValidation.md and doc/en/Decorator.md in the lakutata package (Chinese in doc/zh).
  • @lakutata/entrypoint: the controllers and their HTTP, CLI and service transports.
  • @lakutata/logger: the logger that lakutata presets as log (pino-compatible JSON lines).

Released under the MIT License.