Skip to content

omar-hanafy/initialization_manager

Repository files navigation

initialization_manager

pub package License: MIT

A pure Dart orchestration engine for app bootstrap flows. Define ordered startup work, defer post-auth phases behind named gates, and handle retries, timeouts, and cancellation - all without depending on Flutter, Riverpod, BLoC, or any other framework.

Features

  • Ordered steps with explicit dependencies
  • Named gates for deferred work (not hard-coded phases)
  • Built-in InitializationGate.authenticated convenience gate
  • Per-step shouldRun predicates for conditional execution
  • Per-step or plan-level timeouts
  • Severity-based failure handling: skippable, retryable, fatal
  • Cooperative cancellation with InitializationCancellationToken
  • Append-only execution journal
  • Immutable snapshots with per-step state
  • Aggregate InitializationSummary for high-level reporting
  • Framework-agnostic: inject your own context through the generic host parameter

Installation

Add the dependency to your pubspec.yaml:

dependencies:
  initialization_manager: ^0.1.0

Or run:

dart pub add initialization_manager

Quick start

import 'package:initialization_manager/initialization_manager.dart';

class AppHost {
  Future<void> initializeCore() async {}
  Future<String?> restoreSession() async => null;
  Future<void> preloadGuestContent() async {}
  Future<void> fetchPackages(String token) async {}
}

final engine = InitializationEngine<AppHost>(
  host: AppHost(),
  plan: InitializationPlan<AppHost>(
    steps: <InitializationStep<AppHost>>[
      InitializationStep<AppHost>(
        id: 'core',
        run: (context) => context.host.initializeCore(),
      ),
      InitializationStep<AppHost>(
        id: 'restoreSession',
        dependsOn: const <String>{'core'},
        run: (context) async {
          final token = await context.host.restoreSession();
          if (token != null) {
            context.openGate(InitializationGate.authenticated, value: token);
          }
        },
      ),
      InitializationStep<AppHost>(
        id: 'guestContent',
        dependsOn: const <String>{'core'},
        run: (context) => context.host.preloadGuestContent(),
      ),
      InitializationStep<AppHost>.afterAuthentication(
        id: 'packages',
        dependsOn: const <String>{'restoreSession'},
        run: (context) async {
          final token = context.requireAuthenticatedValue<String>();
          await context.host.fetchPackages(token);
        },
      ),
    ],
  ),
);

engine.snapshotStream.listen((snapshot) {
  if (snapshot.isWaitingForGates) {
    // Navigate to login if that is your app's next step.
  }
});

await engine.start();

How it works

You define one immutable InitializationPlan. Each step can:

  • depend on other step ids
  • wait for one or more gates
  • skip itself with a shouldRun predicate
  • classify its own errors by severity

The engine runs steps in declaration order, respecting dependencies and gate requirements. When no more steps can advance because of closed gates, the lifecycle becomes waitingForGates. When a retryable or fatal failure blocks progress, the lifecycle becomes blocked or failed and the failure details are available through snapshot.lastFailure.

Gates and authentication

Gates let you split bootstrap into multiple phases without hard-coding the order. Both common auth scenarios use the same plan:

  1. Saved session restored during startup - the app opens InitializationGate.authenticated immediately and post-auth steps run on the splash screen.
  2. No saved session - the engine completes the non-auth work, reports waitingForGates, and the host opens the gate later after login.

Create steps that wait for the built-in auth gate with the InitializationStep.afterAuthentication factory, or define custom gates for any deferred phase:

const featureFlags = InitializationGate<Map<String, bool>>('featureFlags');

InitializationStep<AppHost>.afterGate(
  id: 'applyFlags',
  gate: featureFlags,
  run: (context) {
    final flags = context.requireGateValue(featureFlags);
    // Apply feature flags to the host.
  },
);

Failure handling

Every step error is routed through a severity resolver. The three built-in levels control what happens next:

Severity Engine reaction Host action
skippable Records the step as skipped, continues None required
retryable Pauses the run on blocked Call engine.retry()
fatal Stops the run on failed Create a new engine

Provide a custom resolver per step to map specific exceptions:

InitializationStep<AppHost>(
  id: 'analytics',
  run: (context) => initAnalytics(),
  severity: (error, stackTrace) {
    if (error is SocketException) return InitializationSeverity.skippable;
    return InitializationSeverity.retryable;
  },
);

Snapshots and progress

InitializationSnapshot is the detailed runtime view. It includes:

  • lifecycle state and current step
  • open gates and waiting gates
  • counts for completed, pending, failed, skipped, and active work
  • per-step state list with timestamps and failure details
  • a progress ratio over currently active (non-deferred) work

InitializationSummary is the compact top-line view, available through snapshot.summary or engine.summary. It exposes an InitializationCompletionStatus and per-outcome counts, useful for logging, analytics, or deciding how a run ended without inspecting every step.

Key snapshot helpers:

snapshot.isReadyForHandoff  // completed or waiting on deferred gates
snapshot.isFullyComplete    // every step done, no deferred work
snapshot.canRetry           // blocked on a retryable failure
snapshot.progress           // 0.0 to 1.0 over active work

Core types

Type Purpose
InitializationEngine<T> Runtime orchestrator
InitializationPlan<T> Immutable plan definition
InitializationStep<T> Step metadata and execution callback
InitializationStepContext<T> Runtime helpers exposed to a running step
InitializationSnapshot Immutable engine state at a point in time
InitializationSummary Compact aggregate outcome
InitializationFailure Structured failure details
InitializationRecord Append-only journal entry
InitializationGate<T> Typed named gate for deferred phases
InitializationCancellationToken Cooperative cancellation token

Example app

The example/ directory includes a Flutter app with two integrations over the same engine:

  • Riverpod 3 adapter
  • BLoC / Cubit adapter

Both demos show launching with a saved token, launching without a token and opening the auth gate after login, and handling a retryable post-auth failure.

Testing

Run the package tests:

dart test

Generate a local line-coverage report and enforce a threshold:

dart test --coverage=coverage
dart run tool/check_coverage.dart

The checker defaults to a 99% line-coverage target. Use a custom threshold when needed:

dart run tool/check_coverage.dart --min-line=95

If you want branch coverage too, generate it explicitly and set a branch target:

dart test --coverage=coverage --branch-coverage
dart run tool/check_coverage.dart --min-line=99 --min-branch=90

License

MIT - see LICENSE for details.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages