registerAndInitService<TService extends ServiceMixin> method

Resolvable<Unit> registerAndInitService<TService extends ServiceMixin>(
  1. TService service, {
  2. Option<TOnRegisterCallback<TService>> onRegister = const None(),
  3. Option<TOnUnregisterCallback<TService>> onUnregister = const None(),
  4. Entity groupEntity = const DefaultEntity(),
  5. bool enableUntilExactlyK = false,
})
inherited

Registers service and drives its lifecycle:

  • Runs service.init() before the user-supplied onRegister fires, so the user hook always sees a fully-initialised service.
  • On unregister, runs service.dispose() first, then onUnregister.

Pass enableUntilExactlyK true if you intend to wait on this exact registration via untilExactlyK / untilExactlyT.

Implementation

Resolvable<Unit> registerAndInitService<TService extends ServiceMixin>(
  TService service, {
  Option<TOnRegisterCallback<TService>> onRegister = const None(),
  Option<TOnUnregisterCallback<TService>> onUnregister = const None(),
  Entity groupEntity = const DefaultEntity(),
  bool enableUntilExactlyK = false,
}) {
  return register<TService>(
    service,
    onRegister: Some((service) {
      // Run the framework-level `init()` first, then chain the user-supplied
      // onRegister (if any). Without this chain the user's hook would never
      // observe a fully-initialised service.
      return consec(
        awaitCallbackResult(
          service.init(),
          logAndSwallowSyncErr: false,
          logContext: 'registerAndInitService<$TService>.init',
        ),
        (_) {
          return switch (onRegister) {
            Some(value: final userCb) => awaitCallbackResult(
                userCb(service),
                logAndSwallowSyncErr: false,
                logContext:
                    'registerAndInitService<$TService>.userOnRegister',
              ),
            None() => null,
          };
        },
      );
    }),
    onUnregister: Some((serviceResult) {
      // Pattern-match the Result so an Err-resolved service doesn't crash
      // the unregister chain. Err path: skip dispose, but still fire the
      // user's onUnregister with the original Err so they can observe
      // the failed cleanup target.
      return switch (serviceResult) {
        Err() => _fireUserOnUnregister<TService>(
            onUnregister,
            serviceResult,
          ),
        Ok(value: final service) => consec(
            service.dispose().value,
            (disposeResult) {
              if (disposeResult case Err(:final error)) {
                // dispose() failure is logged-and-swallowed here so the
                // user's cleanup hook still gets a chance to run. The
                // underlying error is preserved in the log via
                // `service.dart::recordError`.
                Log.err(
                  'registerAndInitService<$TService>.dispose: $error',
                );
              }
              return _fireUserOnUnregister<TService>(
                onUnregister,
                serviceResult,
              );
            },
          ),
      };
    }),
    groupEntity: groupEntity,
    enableUntilExactlyK: enableUntilExactlyK,
  ).toUnit();
}