ark_error_manager 0.1.0-dev.1 copy "ark_error_manager: ^0.1.0-dev.1" to clipboard
ark_error_manager: ^0.1.0-dev.1 copied to clipboard

Centralized, policy-driven error management for Dart applications.

Ark Error Manager #

Centralized, policy-driven error management for Dart applications.

Ark Error Manager gives an application one explicit error-processing boundary without turning the manager into a dependency of every UseCase, Presenter, repository, data source, or service. The application owns one manager at its composition root and connects the runtime boundaries it actually uses: a root Dart Zone, child isolates, and, through ark_error_manager_flutter, Flutter's framework and platform hooks.

Русская версия

0.1.0-dev.1 is a prerelease. The central pipeline, sanitization boundary, lifecycle, and Flutter adapter are implemented and tested, but public API details may change before the first stable release.

Why this package exists #

Scattered try/catch blocks can recover a local operation, but they do not provide an application-wide answer to these questions:

  • how serious is the failure for the running application;
  • which stable category should appear in diagnostics;
  • which build and deployment environment produced it;
  • what information is safe to persist or transmit;
  • whether the user needs a passive notice or an interrupting presentation;
  • whether an application scope must be reset;
  • what should happen when an error reporter fails while reporting an error.

Ark Error Manager separates these decisions into typed roles. It does not use string categories, does not infer severity from the capture source, and does not send the original error object to external reporters.

Architectural boundary #

Application composition root
  ├── owns ErrorManager
  ├── connects root Zone
  ├── connects child-isolate listeners
  └── optionally connects Flutter global hooks
               │
               ▼
       one ordered error pipeline
               │
      ┌────────┼─────────┐
      ▼        ▼         ▼
  reporters  presenter  recovery

Feature code throws typed exceptions or exposes expected failures through its normal business state. It does not request ErrorManager from DI and does not store the manager in Model, Presenter, Repository, or UseCase.

An expected negative business result is not automatically an exceptional failure. Validation, rejection, and other normal outcomes should remain in a typed Result or business state. The manager handles failures that need an application-level diagnostic, presentation, or recovery decision.

Installation #

dependencies:
  ark_error_manager: ^0.1.0-dev.1
import 'package:ark_error_manager/ark_error_manager.dart';

Minimal Dart bootstrap #

The application creates the Zone. The manager only provides a compatible uncaught-error callback.

import 'dart:async';

import 'package:ark_error_manager/ark_error_manager.dart';

void main() {
  final ErrorManager manager = ErrorManager(
    configuration: ErrorManagerConfiguration(
      environment: const ErrorRuntimeEnvironment(
        buildMode: ErrorBuildMode.release,
        deployment: ErrorDeploymentEnvironment.production,
        application: ErrorApplicationInfo(
          name: 'Example application',
          version: '1.4.0',
          buildNumber: '87',
        ),
      ),
      reporters: const <ErrorReporter>[
        DeveloperLogErrorReporter(),
      ],
    ),
  );

  runZonedGuarded(
    () {
      startApplication();
    },
    manager.onUncaughtZoneError,
  );
}

ErrorManager deliberately has no runGuarded method. The application keeps control over Zone creation, zoneValues, ZoneSpecification, and nested runtime boundaries.

Error assessment #

ErrorCategory #

ErrorCategory is a closed enum:

  • domain — an unexpected domain-invariant violation;
  • application — application orchestration failed;
  • infrastructure — storage, network, or another technical resource failed;
  • integration — a third-party service, SDK, or plugin failed;
  • framework — Dart, Flutter, or another framework reported a contract error;
  • configuration — required configuration is invalid or incomplete;
  • lifecycle — initialization, shutdown, or disposal failed;
  • unknown — no more precise category was established.

Applications extend classification through ErrorClassifier, not by adding uncontrolled string values to the category vocabulary:

final class SessionExpiredClassifier
    extends TypedErrorClassifier<SessionExpiredException> {
  const SessionExpiredClassifier();

  @override
  ErrorAssessment classifyError(
    SessionExpiredException error,
    ErrorOccurrence occurrence,
    ErrorRuntimeEnvironment environment,
  ) {
    return const ErrorAssessment(
      category: ErrorCategory.application,
      severity: ErrorSeverity.critical,
    );
  }
}

Classifiers are ordered. The first supporting classifier owns the occurrence; the built-in fallback produces unknown and critical.

ErrorSeverity #

Severity describes application impact, not where the error was caught:

  • recoverable — one operation failed, but state remains consistent;
  • degraded — the application continues with reduced functionality;
  • critical — a significant scope or subsystem needs controlled recovery;
  • fatal — the process or isolate cannot safely continue.

Derived properties such as applicationMayContinue and requiresApplicationTermination are provided by ErrorSeverityAssessment. There is no independent isFatal flag that can contradict the enum.

ErrorCaptureSource is a separate dimension: zone, framework, platform, isolate, or reported. A critical error remains critical regardless of the boundary that observed it.

The processing pipeline #

One accepted occurrence moves through these stages in order:

  1. classifiers produce ErrorAssessment;
  2. context providers collect structured runtime context;
  3. policy creates ErrorActionPlan;
  4. sanitizer creates a report safe for reporters;
  5. independent reporters record the sanitized report;
  6. an optional presenter performs the user-facing action;
  7. an optional recovery controller performs application-level recovery;
  8. observers receive lifecycle notifications.

Occurrences are processed sequentially by one manager instance. A slow report cannot reorder a later incident ahead of an earlier one.

Pipeline components are isolated. If one reporter throws, the remaining reporters still run. Pipeline failures go to ErrorPipelineFailureHandler and are never submitted back to the same manager recursively.

Reporting, presentation, and recovery #

ErrorPolicy returns one explicit ErrorActionPlan:

const ErrorActionPlan(
  reporting: ErrorReportingDirective.record,
  presentation: ErrorPresentationDirective.blocking,
  recovery: ErrorRecoveryDirective.resetScope,
)

These actions are separate because they answer different questions:

  • reporting decides whether diagnostics must be persisted or transmitted;
  • presentation decides what the active user should see;
  • recovery decides how the application restores a safe state.

The built-in StandardErrorPolicy records every accepted occurrence, adapts presentation intensity to severity and build mode, and requests termination only for fatal. Applications can replace it with a policy that also inspects the concrete error type, category, environment, or operation.

Reporters receive ErrorReport, not ErrorIncident. The report does not contain the original error object or source-specific raw details. Presenter and recovery remain local to the application and receive the raw incident when they need typed decisions.

Runtime environment #

Two independent values describe the runtime:

  • ErrorBuildMode: debug, profile, or release;
  • ErrorDeploymentEnvironment: development, staging, production, or test.

Compilation mode and deployment target are not interchangeable. A profile build can run against staging, and a release build can run in an internal development environment.

ErrorApplicationInfo adds the application name, version, optional build number, and package identifier. Flutter build-mode detection is provided by ark_error_manager_flutter.

Security boundary #

StrictErrorSanitizer is the default sanitizer. It:

  • masks fields whose names indicate authorization, cookies, passwords, secrets, tokens, API keys, or sessions;
  • recursively sanitizes nested maps and iterables;
  • sanitizes the operation and stack-trace text;
  • replaces the original error message with its runtime type in release mode.

Custom reporters must not receive ErrorIncident. Keep raw request headers, cookies, credentials, complete request bodies, and personal data out of ErrorContextSection. Sanitization is a safety boundary, not permission to collect everything first.

See Security and privacy for the complete model.

Handled errors #

A global boundary cannot observe an error that local code catches and fully consumes. There are only three honest outcomes:

  1. the local boundary recovers and the error needs no global processing;
  2. it transforms or rethrows the failure so that normal propagation continues;
  3. it deliberately reports the caught error through handleError or captureError.

The third option belongs at a real integration boundary that must suppress propagation. It is not a reason to inject ErrorManager into every business object.

Child isolates #

Root-isolate hooks do not receive errors from child isolates automatically. Attach one bridge when the application creates an isolate:

final Isolate worker = await Isolate.spawn(startWorker, configuration);
final IsolateErrorManagerBinding binding = IsolateErrorManagerBinding(
  isolate: worker,
  manager: manager,
)..attach();

// Before releasing ownership of the isolate:
binding.detach();

The bridge is owned beside the isolate. Worker code does not import or look up the manager.

Lifecycle #

  • captureError accepts work without requiring the caller to await processing;
  • handleError completes after all planned actions finish;
  • flush waits for all currently accepted occurrences;
  • close stops accepting new work and drains the existing pipeline;
  • repeated work after close is rejected.

Call flush before a controlled shutdown when reports must finish. Call close when the application permanently releases the manager.

Package relationships #

Ark Error Manager has no dependency on UseCase Forge, Ark MVP, Ark DI, or Ark Data Layer. Those packages already propagate unexpected errors through Stream, Flutter, or Zone boundaries. Application-specific error types can be classified centrally without introducing reverse dependencies between packages.

Further reading #

License #

Apache License 2.0. See LICENSE and NOTICE.