中文

Errors and lifecycle

Understand Func's built-in error pipeline and resource lifecycle.

Updated

Without any additional handling, func applies its built-in Exception behavior. Applications may redirect those failures to the appropriate stream or suppress them when their protocol requires it.

Exceptions and defaults

The built-in Exception exposes stable code, scope, details, message, and cause fields without defining a JSON wire format. Application code may follow that structure or throw an ordinary native Error.

Correctable CLI input failures and ordinary Handler errors set exit code 1 and use a minimal one-line stderr route by default. The fallback adds no color, border, or table. Framework failures such as system-definition errors and Provider initialization errors (F_SYSTEM) continue to throw unless an application filter handles them explicitly.

Catch and handle errors

A Command or Module implements OnError to handle failures in its own scope. onError has a fixed signature rather than being a lifecycle method, and parameter decorators are not used: the framework always passes the Exception and current invocation Context.

src/commands/publish.command.ts
import { Command, Handler } from 'func'
import type { Context, Exception, OnError } from 'func'

@Command('publish')
export class PublishCommand implements OnError {
  @Handler()
  run() {
    throw new Error('Registry is unavailable')
  }

  onError(exception: Exception, context: Context) {
    context.io.stderr.write(`Publish failed: ${exception.message}\n`)
  }
}

Returning handles the failure; the return value is ignored. Throwing continues with the next outer onError, without re-entering the current instance.

Error propagation order

For a selected Command, failures propagate in this order:

  • The current Command, including an Option Command or Missing Command.
  • The Module that directly owns the Command.
  • The root Module.

A Module’s own onInit or onDispose failure starts at that Module and travels toward the root. A business error thrown by a Service follows the Command or Module that called it, while a Service lifecycle failure begins at the Service’s owner Module. Parse failures that occur before command selection reach the root Module only.

src/app.module.ts
import { Module } from 'func'
import type { Context, Exception, OnError } from 'func'
import { PublishCommand } from './commands/publish.command'

@Module({
  commands: [PublishCommand],
})
export class AppModule implements OnError {
  onError(exception: Exception, context: Context) {
    context.io.stderr.write(`${exception.message}\n`)
  }
}

During an early parse failure, Context may not contain a resolved command. Check context.invocation.resolved first. When it is false, do not read command arguments; when true, parsed information is available from context.invocation.args, context.command, and context.options.

Put errors in a JSON response

func does not define an error JSON schema or serialize Exception automatically. A module that imports JsonOutputModule can inject JsonOutput and explicitly send the fields the application permits from onError:

src/app.module.ts
import { Module } from 'func'
import type { Exception, OnError } from 'func'
import { JsonOutput, JsonOutputModule } from 'func/json-output'

@Module({ imports: [JsonOutputModule] })
export class AppModule implements OnError {
  constructor(private readonly output: JsonOutput) {}

  onError(exception: Exception) {
    this.output.json({
      error: {
        code: exception.code,
        message: exception.message,
      },
    })
  }
}

The application decides whether to expose details, cause, or a stack, and it remains free to design its own error code schema. This avoids locking the application into a framework protocol or disclosing internal information unexpectedly.

See JSON output for feature setup, stdout behavior, and serialization boundaries.

Lifecycle

onInit and onDispose are the resource lifecycle for func applications. Commands, Modules, and Services may use them to initialize resources, release them, or record performance data:

src/app.module.ts
import { Module } from 'func'
import type { OnDispose, OnInit } from 'func'

@Module({})
export class AppModule implements OnInit, OnDispose {
  onInit() {
    // This invocation is initialized.
  }

  onDispose() {
    // Release resources owned by this instance.
  }
}

Lifecycle methods do not receive the invocation result, and their return values are ignored.